├── .github └── workflows │ └── node.js-test.yml ├── .gitignore ├── README.md ├── adhoc.png ├── clipboard-flow.gif ├── cursor.gif ├── data.json ├── flow_links.gif ├── highlight.gif ├── main.js ├── manifest.json ├── onoff.gif ├── package.json ├── rollup.config.js ├── secret.png ├── secrettips.png ├── src ├── ExtractHighlightsPluginSettings.ts ├── ExtractHighlightsPluginSettingsTab.ts ├── ProcessHighlights.ts ├── ToggleHighlight.ts └── main.ts ├── styles.css ├── test ├── ProcessHighlightsTest.ts └── ToggleHighlightTest.ts ├── tsconfig.json ├── tsconfig.testing.json └── video.png /.github/workflows/node.js-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Intellij 4 | *.iml 5 | .idea 6 | 7 | # npm 8 | node_modules 9 | package-lock.json 10 | 11 | # build 12 | # main.js 13 | *.js.map 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Extract Highlights Plugin 2 | ![](https://github.com/akaalias/extract-highlights-plugin/workflows/Node.js%20CI/badge.svg) 3 | 4 | Create and extract highlights from a current markdown note in Obsidian into your clipboard. Based on [icebear's plugin request](https://forum.obsidian.md/t/extract-highlights-from-note/7867). 5 | 6 | ## Training Videos 7 | 8 | ### Watch: The Quick-Start Tutorial! 9 | 10 | [![](https://github.com/akaalias/extract-highlights-plugin/blob/master/adhoc.png?raw=true)](https://youtu.be/KWdEatdD2bo) 11 | 12 | ### Watch: A SECRET NEW highlight mode 13 | 14 | [![](https://github.com/akaalias/extract-highlights-plugin/blob/master/secret.png?raw=true)](https://youtu.be/5kkcqAn6joU) 15 | 16 | ### Watch: Tips and Tricks for the SECRET mode 17 | 18 | [![](https://github.com/akaalias/extract-highlights-plugin/blob/master/secrettips.png?raw=true)](https://youtu.be/n3YW5bmnETg) 19 | 20 | ### How it works 21 | This plugin will copy the highlights delimited by `==`, `**` and `` into your clipboard as a bullet-list. 22 | 23 | Optionally you can customize... 24 | 25 | * **The heading text of the list**, include the note-title or hide it all-together 26 | * **Adding a footnote** to each that link back to the source-file 27 | * **Creating an ad-hoc map-of-content (MOC)** by turning each highlight into an `[[` Obsidian link `]]` 28 | * **Auto-capitalize** the first letter in each highlight for consistency 29 | 30 | ### Demo Creating and Extracting Highlights 31 | 32 | 1. First you see how I use the CREATE highlights hot-key to highlight sentences 33 | 2. Then you see how I use the EXTRACT highlights hot-key to create a new file with the highlights 34 | 35 | ![basic functionality](https://github.com/akaalias/extract-highlights-plugin/blob/master/highlight.gif?raw=true) 36 | 37 | #### Using the Hotkey to HIGHLIGHT (and UN-HIGHLIGHT) the sentence under cursor 38 | 39 | The default hotkey for this is: 40 | 41 | SHIFT + ALT + _ 42 | 43 | Super useful for when you're reading and just don't want to switch to your mouse for selecting the sentence. 44 | 45 | ![demo](https://github.com/akaalias/extract-highlights-plugin/blob/master/onoff.gif?raw=true) 46 | 47 | Will remove highlighting if the sentence under your cursor is currently delimited by "==". 48 | 49 | #### Using the Hotkey to EXTRACT highlights 50 | 51 | The default hotkey is: 52 | 53 | SHIFT + ALT + = 54 | 55 | #### Using the Ribbon Button 56 | There is also a button (a circle-shape) that's added to your left-side ribbon. 57 | 58 | Clicking on it will also extract all highlighted parts in your current note and place it in your clipboard. 59 | 60 | #### Using the Command Palette 61 | I looked into it and there’s a bug the way clipboard works with the Command Palette. Basically everything but the “Paste” works. 62 | 63 | But I’ve found a temporary work-around. It’s weird but it works. 64 | 65 | 1. Trigger Command Palette (Command-P) 66 | 2. Find “Extract Highlights” 67 | 3. Hit Enter (You should see a notification (“Highlights copied to clipboard!”) 68 | 4. Workaround: At this point, briefly switch to a different note and back (This materialises the clipboard data for pasting) 69 | 5. Paste works now the same as with the Hotkey and Button-press 70 | 71 | #### Pasting highlights from your clipboard 72 | 73 | After using the hotkey, button or command palette, anywhere you want, just paste the clipboard! 74 | 75 | Command + v (MacOS) or the equivalent on Windows/Linux 76 | 77 | The output is a markdown-block titled "Highlights" with a bullet-list of the highlights. 78 | 79 | ### Feedback 80 | Are you using Extract Highlights? I'd love to hear from you! 81 | 82 | [Share your questions and suggestions in the forum](https://forum.obsidian.md/t/extract-highlights-plugin/8763/12) 83 | 84 | 85 | ### Backlog 86 | #### TODO 87 | - Record video on using the "explosion mode" for research and creating atomic notes 88 | - Pre-requisites 89 | - Highlights Plugin 90 | - Create links 91 | - Create page 92 | - Enable explode mode 93 | - Open notes on creation 94 | - Sliding Panes Plugin 95 | - Start with a good article (Economist) 96 | - Go through and highlight sentences 97 | - Create MOC and explode into notes 98 | - BOOOMMMMM!!! 99 | - You've got an MOC 100 | - You've got the core for single-idea, atomic notes 101 | - You've got a backlink to the original file 102 | 103 | #### DOING 104 | ... 105 | 106 | #### DONE 107 | - [x] "Explode" highlights into individual notes (assumes I'm creating the list of links as well) 108 | - [x] command (SHIFT + ALT + =) which then copies all of the highlighted text either into: 109 | - [x] click a button which then copies all of the highlighted text either into: 110 | - [x] allow for `` to be used as highlights 111 | - [x] allow for standard bold (`**`) to be used as highlights 112 | - [x] allow to optionally include or completely exclude `## Highlights` heading 113 | - [x] allow to change text in heading `## My Custom Highlights` 114 | - [x] allow to include note-name in heading such as `## From: $NOTE_TITLE` 115 | - [x] allow to add footnotes for each highlight and include link to source-note 116 | - [x] allow to optionally enable bold for highlights 117 | - [x] allow for Command Palette to trigger copying (Works sort of, bug in Electron) 118 | - [x] my clipboard 119 | - [x] a new note 120 | -------------------------------------------------------------------------------- /adhoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/adhoc.png -------------------------------------------------------------------------------- /clipboard-flow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/clipboard-flow.gif -------------------------------------------------------------------------------- /cursor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/cursor.gif -------------------------------------------------------------------------------- /data.json: -------------------------------------------------------------------------------- 1 | {"headlineText":"","addFootnotes":false,"useBoldForHighlights":false,"createLinks":true,"autoCapitalize":true,"createNewFile":true,"explodeIntoNotes":true,"openExplodedNotes":true,"createContextualQuotes":true} -------------------------------------------------------------------------------- /flow_links.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/flow_links.gif -------------------------------------------------------------------------------- /highlight.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/highlight.gif -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var obsidian = require('obsidian'); 4 | 5 | /*! ***************************************************************************** 6 | Copyright (c) Microsoft Corporation. 7 | 8 | Permission to use, copy, modify, and/or distribute this software for any 9 | purpose with or without fee is hereby granted. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | PERFORMANCE OF THIS SOFTWARE. 18 | ***************************************************************************** */ 19 | /* global Reflect, Promise */ 20 | 21 | var extendStatics = function(d, b) { 22 | extendStatics = Object.setPrototypeOf || 23 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 24 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 25 | return extendStatics(d, b); 26 | }; 27 | 28 | function __extends(d, b) { 29 | extendStatics(d, b); 30 | function __() { this.constructor = d; } 31 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 32 | } 33 | 34 | function __awaiter(thisArg, _arguments, P, generator) { 35 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 36 | return new (P || (P = Promise))(function (resolve, reject) { 37 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 38 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 39 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 40 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 41 | }); 42 | } 43 | 44 | function __generator(thisArg, body) { 45 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 46 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 47 | function verb(n) { return function (v) { return step([n, v]); }; } 48 | function step(op) { 49 | if (f) throw new TypeError("Generator is already executing."); 50 | while (_) try { 51 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 52 | if (y = 0, t) op = [op[0] & 2, t.value]; 53 | switch (op[0]) { 54 | case 0: case 1: t = op; break; 55 | case 4: _.label++; return { value: op[1], done: false }; 56 | case 5: _.label++; y = op[1]; op = [0]; continue; 57 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 58 | default: 59 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 60 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 61 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 62 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 63 | if (t[2]) _.ops.pop(); 64 | _.trys.pop(); continue; 65 | } 66 | op = body.call(thisArg, _); 67 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 68 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 69 | } 70 | } 71 | 72 | var ExtractHighlightsPluginSettings = /** @class */ (function () { 73 | function ExtractHighlightsPluginSettings() { 74 | this.headlineText = ""; 75 | this.addFootnotes = false; 76 | this.useBoldForHighlights = false; 77 | this.createLinks = false; 78 | this.autoCapitalize = false; 79 | this.createNewFile = false; 80 | this.explodeIntoNotes = false; 81 | this.openExplodedNotes = false; 82 | this.createContextualQuotes = false; 83 | } 84 | return ExtractHighlightsPluginSettings; 85 | }()); 86 | 87 | var ExtractHighlightsPluginSettingsTab = /** @class */ (function (_super) { 88 | __extends(ExtractHighlightsPluginSettingsTab, _super); 89 | function ExtractHighlightsPluginSettingsTab(app, plugin) { 90 | var _this = _super.call(this, app, plugin) || this; 91 | _this.plugin = plugin; 92 | return _this; 93 | } 94 | ExtractHighlightsPluginSettingsTab.prototype.display = function () { 95 | var _this = this; 96 | var containerEl = this.containerEl; 97 | containerEl.empty(); 98 | containerEl.createEl("h2", { text: "Extract Highlights Plugin" }); 99 | new obsidian.Setting(containerEl) 100 | .setName("Heading Text") 101 | .setDesc("If set, will add `## Your Text`. Use $NOTE_TITLE to include title. Leave blank to omit. ") 102 | .addText(function (text) { 103 | return text 104 | .setPlaceholder("Highlights for $NOTE_TITLE") 105 | .setValue(_this.plugin.settings.headlineText) 106 | .onChange(function (value) { 107 | _this.plugin.settings.headlineText = value; 108 | _this.plugin.saveData(_this.plugin.settings); 109 | }); 110 | }); 111 | new obsidian.Setting(containerEl) 112 | .setName('Use bold for highlights') 113 | .setDesc('If enabled, will include classic markdown bold (**) sections as highlights') 114 | .addToggle(function (toggle) { 115 | return toggle.setValue(_this.plugin.settings.useBoldForHighlights).onChange(function (value) { 116 | _this.plugin.settings.useBoldForHighlights = value; 117 | _this.plugin.saveData(_this.plugin.settings); 118 | }); 119 | }); 120 | new obsidian.Setting(containerEl) 121 | .setName('Enable Footnotes') 122 | .setDesc('If enabled, will add a footnote to the current document to each highlight in your list. Useful when you wan to keep track of which highlight came from which source file.') 123 | .addToggle(function (toggle) { 124 | return toggle.setValue(_this.plugin.settings.addFootnotes).onChange(function (value) { 125 | _this.plugin.settings.addFootnotes = value; 126 | _this.plugin.saveData(_this.plugin.settings); 127 | }); 128 | }); 129 | new obsidian.Setting(containerEl) 130 | .setName('Auto-capitalize first letter') 131 | .setDesc('If enabled, capitalizes the first letter of each highlight.') 132 | .addToggle(function (toggle) { 133 | return toggle.setValue(_this.plugin.settings.autoCapitalize).onChange(function (value) { 134 | _this.plugin.settings.autoCapitalize = value; 135 | _this.plugin.saveData(_this.plugin.settings); 136 | }); 137 | }); 138 | new obsidian.Setting(containerEl) 139 | .setName('Create links') 140 | .setDesc('If enabled, will turn each highlight into a [[ link ]] to create a highlight MOC') 141 | .addToggle(function (toggle) { 142 | return toggle.setValue(_this.plugin.settings.createLinks).onChange(function (value) { 143 | _this.plugin.settings.createLinks = value; 144 | // disable explode notes mode 145 | if (_this.plugin.settings.explodeIntoNotes && value == false) { 146 | _this.plugin.settings.explodeIntoNotes = false; 147 | _this.plugin.settings.openExplodedNotes = false; 148 | } 149 | _this.plugin.saveData(_this.plugin.settings); 150 | }); 151 | }); 152 | new obsidian.Setting(containerEl) 153 | .setName('Open new file with highlights') 154 | .setDesc('If enabled, opens a new file with the highlights copied into.') 155 | .addToggle(function (toggle) { 156 | return toggle.setValue(_this.plugin.settings.createNewFile).onChange(function (value) { 157 | _this.plugin.settings.createNewFile = value; 158 | // disable explode notes mode 159 | if (_this.plugin.settings.explodeIntoNotes && value == false) { 160 | _this.plugin.settings.explodeIntoNotes = false; 161 | _this.plugin.settings.openExplodedNotes = false; 162 | } 163 | _this.plugin.saveData(_this.plugin.settings); 164 | }); 165 | }); 166 | containerEl.createEl("h2", { text: "💥 Explode Notes Mode 💥" }); 167 | containerEl.createEl("p", { text: "A secret mode that will take your highlighting to the next level. Only available if you have 'Create Links' and 'Create new File' enabled. After enabling both, close this window and open again to see options." }); 168 | if (this.plugin.settings.createLinks && this.plugin.settings.createNewFile) { 169 | new obsidian.Setting(containerEl) 170 | .setName('Explode links into notes') 171 | .setDesc('If enabled, will turn each highlight into a note with the highlighted text as quote and a backlink to the MOC and source-file. Very powerful but use with caution!') 172 | .addToggle(function (toggle) { 173 | return toggle.setValue(_this.plugin.settings.explodeIntoNotes).onChange(function (value) { 174 | _this.plugin.settings.explodeIntoNotes = value; 175 | _this.plugin.saveData(_this.plugin.settings); 176 | }); 177 | }); 178 | new obsidian.Setting(containerEl) 179 | .setName('Open exploded notes on creation') 180 | .setDesc('If enabled, will open each of your exploded notes when you create them. Fun and useful to continue working in your highlight-notes right away!') 181 | .addToggle(function (toggle) { 182 | return toggle.setValue(_this.plugin.settings.openExplodedNotes).onChange(function (value) { 183 | _this.plugin.settings.openExplodedNotes = value; 184 | _this.plugin.saveData(_this.plugin.settings); 185 | }); 186 | }); 187 | new obsidian.Setting(containerEl) 188 | .setName('Create contextual quotes') 189 | .setDesc('If enabled, will quote the full line of your highlight, not just the highlight itself. Useful for keeping the context of your highlight.') 190 | .addToggle(function (toggle) { 191 | return toggle.setValue(_this.plugin.settings.createContextualQuotes).onChange(function (value) { 192 | _this.plugin.settings.createContextualQuotes = value; 193 | _this.plugin.saveData(_this.plugin.settings); 194 | }); 195 | }); 196 | } 197 | }; 198 | return ExtractHighlightsPluginSettingsTab; 199 | }(obsidian.PluginSettingTab)); 200 | 201 | var ToggleHighlight = /** @class */ (function () { 202 | function ToggleHighlight() { 203 | } 204 | ToggleHighlight.prototype.toggleHighlight = function (s, ch) { 205 | if (s == "") 206 | return ""; 207 | if (s.indexOf(".") < 0) { 208 | return "==" + s + "=="; 209 | } 210 | var left = s.substring(0, ch); 211 | var right = s.substring(ch); 212 | var marked = left + "$CURSOR$" + right; 213 | // https://regex101.com/r/BSpvV6/7 214 | // https://stackoverflow.com/a/5553924 215 | var p = marked.match(/(==(.*?)==)|[^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$)/gm); 216 | var np = new Array(); 217 | if (p.length > 0) { 218 | p.forEach(function (part) { 219 | if (typeof part !== 'undefined') { 220 | if (part.trim() == "") { 221 | return; 222 | } 223 | if (part.includes("$CURSOR$")) { 224 | if (part.startsWith("==") && part.endsWith("==")) { 225 | part = part.replace(/==/g, ""); 226 | } 227 | else { 228 | part = "==" + part + "=="; 229 | } 230 | part = part.replace("$CURSOR$", ""); 231 | part = part.trim(); 232 | } 233 | part = part.trim(); 234 | np.push(part); 235 | } 236 | }); 237 | return np.join(" "); 238 | } 239 | }; 240 | return ToggleHighlight; 241 | }()); 242 | 243 | obsidian.addIcon('target', ''); 244 | var ExtractHighlightsPlugin = /** @class */ (function (_super) { 245 | __extends(ExtractHighlightsPlugin, _super); 246 | function ExtractHighlightsPlugin() { 247 | return _super !== null && _super.apply(this, arguments) || this; 248 | } 249 | ExtractHighlightsPlugin.prototype.onload = function () { 250 | return __awaiter(this, void 0, void 0, function () { 251 | var _this = this; 252 | return __generator(this, function (_a) { 253 | this.counter = 0; 254 | this.loadSettings(); 255 | this.addSettingTab(new ExtractHighlightsPluginSettingsTab(this.app, this)); 256 | this.statusBar = this.addStatusBarItem(); 257 | this.addRibbonIcon('target', 'Extract Highlights', function () { 258 | _this.extractHighlights(); 259 | }); 260 | this.addCommand({ 261 | id: "shortcut-extract-highlights", 262 | name: "Shortcut for extracting highlights", 263 | callback: function () { return _this.extractHighlights(); }, 264 | hotkeys: [ 265 | { 266 | modifiers: ["Alt", "Shift"], 267 | key: "±", 268 | }, 269 | ], 270 | }); 271 | this.addCommand({ 272 | id: "shortcut-highlight-sentence", 273 | name: "Shortcut for highlighting sentence cursor is in", 274 | callback: function () { return _this.createHighlight(); }, 275 | hotkeys: [ 276 | { 277 | modifiers: ["Alt", "Shift"], 278 | key: "—", 279 | }, 280 | ], 281 | }); 282 | return [2 /*return*/]; 283 | }); 284 | }); 285 | }; 286 | ExtractHighlightsPlugin.prototype.loadSettings = function () { 287 | var _this = this; 288 | this.settings = new ExtractHighlightsPluginSettings(); 289 | (function () { return __awaiter(_this, void 0, void 0, function () { 290 | var loadedSettings; 291 | return __generator(this, function (_a) { 292 | switch (_a.label) { 293 | case 0: return [4 /*yield*/, this.loadData()]; 294 | case 1: 295 | loadedSettings = _a.sent(); 296 | if (loadedSettings) { 297 | // console.log("Found existing settings file"); 298 | this.settings.headlineText = loadedSettings.headlineText; 299 | this.settings.addFootnotes = loadedSettings.addFootnotes; 300 | this.settings.createLinks = loadedSettings.createLinks; 301 | this.settings.autoCapitalize = loadedSettings.autoCapitalize; 302 | this.settings.createNewFile = loadedSettings.createNewFile; 303 | this.settings.explodeIntoNotes = loadedSettings.explodeIntoNotes; 304 | this.settings.openExplodedNotes = loadedSettings.openExplodedNotes; 305 | this.settings.createContextualQuotes = loadedSettings.createContextualQuotes; 306 | } 307 | else { 308 | // console.log("No settings file found, saving..."); 309 | this.saveData(this.settings); 310 | } 311 | return [2 /*return*/]; 312 | } 313 | }); 314 | }); })(); 315 | }; 316 | ExtractHighlightsPlugin.prototype.extractHighlights = function () { 317 | var _a, _b; 318 | return __awaiter(this, void 0, void 0, function () { 319 | var activeLeaf, name, processResults, highlightsText, highlights, baseNames, contexts, saveStatus, newBasenameMOC, i, content, newBasename, e_1; 320 | return __generator(this, function (_c) { 321 | switch (_c.label) { 322 | case 0: 323 | activeLeaf = (_a = this.app.workspace.activeLeaf) !== null && _a !== void 0 ? _a : null; 324 | name = activeLeaf === null || activeLeaf === void 0 ? void 0 : activeLeaf.view.file.basename; 325 | _c.label = 1; 326 | case 1: 327 | _c.trys.push([1, 12, , 13]); 328 | if (!((_b = activeLeaf === null || activeLeaf === void 0 ? void 0 : activeLeaf.view) === null || _b === void 0 ? void 0 : _b.data)) return [3 /*break*/, 10]; 329 | processResults = this.processHighlights(activeLeaf.view); 330 | highlightsText = processResults.markdown; 331 | highlights = processResults.highlights; 332 | baseNames = processResults.baseNames; 333 | contexts = processResults.contexts; 334 | saveStatus = this.saveToClipboard(highlightsText); 335 | new obsidian.Notice(saveStatus); 336 | newBasenameMOC = "Highlights for " + name + ".md"; 337 | if (!this.settings.createNewFile) return [3 /*break*/, 4]; 338 | // Add link back to Original 339 | highlightsText += "## Source\n- [[" + name + "]]"; 340 | return [4 /*yield*/, this.saveToFile(newBasenameMOC, highlightsText)]; 341 | case 2: 342 | _c.sent(); 343 | return [4 /*yield*/, this.app.workspace.openLinkText(newBasenameMOC, newBasenameMOC, true)]; 344 | case 3: 345 | _c.sent(); 346 | _c.label = 4; 347 | case 4: 348 | if (!(this.settings.createNewFile && this.settings.createLinks && this.settings.explodeIntoNotes)) return [3 /*break*/, 9]; 349 | i = 0; 350 | _c.label = 5; 351 | case 5: 352 | if (!(i < baseNames.length)) return [3 /*break*/, 9]; 353 | content = ""; 354 | // add highlight as quote 355 | content += "## Source\n"; 356 | if (this.settings.createContextualQuotes) { 357 | // context quote 358 | content += "> " + contexts[i] + "[^1]"; 359 | } 360 | else { 361 | // regular highlight quote 362 | content += "> " + highlights[i] + "[^1]"; 363 | } 364 | content += "\n\n"; 365 | content += "[^1]: [[" + name + "]]"; 366 | content += "\n"; 367 | newBasename = baseNames[i] + ".md"; 368 | return [4 /*yield*/, this.saveToFile(newBasename, content)]; 369 | case 6: 370 | _c.sent(); 371 | if (!this.settings.openExplodedNotes) return [3 /*break*/, 8]; 372 | return [4 /*yield*/, this.app.workspace.openLinkText(newBasename, newBasename, true)]; 373 | case 7: 374 | _c.sent(); 375 | _c.label = 8; 376 | case 8: 377 | i++; 378 | return [3 /*break*/, 5]; 379 | case 9: return [3 /*break*/, 11]; 380 | case 10: 381 | new obsidian.Notice("No highlights to extract."); 382 | _c.label = 11; 383 | case 11: return [3 /*break*/, 13]; 384 | case 12: 385 | e_1 = _c.sent(); 386 | console.log(e_1.message); 387 | return [3 /*break*/, 13]; 388 | case 13: return [2 /*return*/]; 389 | } 390 | }); 391 | }); 392 | }; 393 | ExtractHighlightsPlugin.prototype.saveToFile = function (filePath, mdString) { 394 | return __awaiter(this, void 0, void 0, function () { 395 | var fileExists; 396 | return __generator(this, function (_a) { 397 | switch (_a.label) { 398 | case 0: return [4 /*yield*/, this.app.vault.adapter.exists(filePath)]; 399 | case 1: 400 | fileExists = _a.sent(); 401 | if (!fileExists) return [3 /*break*/, 2]; 402 | return [3 /*break*/, 4]; 403 | case 2: return [4 /*yield*/, this.app.vault.create(filePath, mdString)]; 404 | case 3: 405 | _a.sent(); 406 | _a.label = 4; 407 | case 4: return [2 /*return*/]; 408 | } 409 | }); 410 | }); 411 | }; 412 | ExtractHighlightsPlugin.prototype.processHighlights = function (view) { 413 | var re; 414 | if (this.settings.useBoldForHighlights) { 415 | re = /(==|\|\*\*)([\s\S]*?)(==|\<\/mark\>|\*\*)/g; 416 | } 417 | else { 418 | re = /(==|\)([\s\S]*?)(==|\<\/mark\>)/g; 419 | } 420 | var markdownText = view.data; 421 | var basename = view.file.basename; 422 | var matches = markdownText.match(re); 423 | this.counter += 1; 424 | var result = ""; 425 | var highlights = []; 426 | var baseNames = []; 427 | var contexts = []; 428 | var lines = markdownText.split("\n"); 429 | var cleanedLines = []; 430 | for (var i = 0; i < lines.length; i++) { 431 | if (!(lines[i] == "")) { 432 | cleanedLines.push(lines[i]); 433 | } 434 | } 435 | if (matches != null) { 436 | if (this.settings.headlineText != "") { 437 | var text = this.settings.headlineText.replace(/\$NOTE_TITLE/, "" + basename); 438 | result += "## " + text + "\n"; 439 | } 440 | for (var _i = 0, matches_1 = matches; _i < matches_1.length; _i++) { 441 | var entry = matches_1[_i]; 442 | // Keep surrounding paragraph for context 443 | if (this.settings.createContextualQuotes) { 444 | for (var i = 0; i < cleanedLines.length; i++) { 445 | var match = cleanedLines[i].match(entry); 446 | if (!(match == null) && match.length > 0) { 447 | var val = cleanedLines[i]; 448 | if (!contexts.contains(val)) { 449 | contexts.push(val); 450 | } 451 | } 452 | } 453 | } 454 | // Clean up highlighting match 455 | var removeNewline = entry.replace(/\n/g, " "); 456 | var removeHighlightStart = removeNewline.replace(/==/g, ""); 457 | var removeHighlightEnd = removeHighlightStart.replace(/\/g, ""); 458 | var removeMarkClosing = removeHighlightEnd.replace(/\<\/mark\>/g, ""); 459 | var removeBold = removeMarkClosing.replace(/\*\*/g, ""); 460 | var removeDoubleSpaces = removeBold.replace(" ", " "); 461 | removeDoubleSpaces = removeDoubleSpaces.replace(" ", " "); 462 | removeDoubleSpaces = removeDoubleSpaces.trim(); 463 | if (this.settings.autoCapitalize) { 464 | if (removeDoubleSpaces != null) { 465 | removeDoubleSpaces = this.capitalizeFirstLetter(removeDoubleSpaces); 466 | } 467 | } 468 | result += "- "; 469 | if (this.settings.createLinks) { 470 | // First, sanitize highlight to be used as a file-link 471 | // * " \ / | < > : ? 472 | var sanitized = removeDoubleSpaces.replace(/\*|\"|\\|\/|\<|\>|\:|\?|\|/gm, ""); 473 | sanitized = sanitized.trim(); 474 | var baseName = sanitized; 475 | if (baseName.length > 100) { 476 | baseName = baseName.substr(0, 99); 477 | baseName += "..."; 478 | } 479 | result += "[[" + baseName + "]]"; 480 | highlights.push(sanitized); 481 | baseNames.push(baseName); 482 | } 483 | else { 484 | result += removeDoubleSpaces; 485 | highlights.push(removeDoubleSpaces); 486 | } 487 | if (this.settings.addFootnotes) { 488 | result += "[^" + this.counter + "]"; 489 | } 490 | result += "\n"; 491 | } 492 | if (this.settings.addFootnotes) { 493 | result += "\n"; 494 | result += "[^" + this.counter + "]: [[" + basename + "]]\n"; 495 | } 496 | result += "\n"; 497 | } 498 | return { markdown: result, baseNames: baseNames, highlights: highlights, contexts: contexts }; 499 | }; 500 | ExtractHighlightsPlugin.prototype.saveToClipboard = function (data) { 501 | if (data.length > 0) { 502 | navigator.clipboard.writeText(data); 503 | return "Highlights copied to clipboard!"; 504 | } 505 | else { 506 | return "No highlights found"; 507 | } 508 | }; 509 | ExtractHighlightsPlugin.prototype.createHighlight = function () { 510 | var mdView = this.app.workspace.activeLeaf.view; 511 | var doc = mdView.sourceMode.cmEditor; 512 | this.editor = doc; 513 | var cursorPosition = this.editor.getCursor(); 514 | var lineText = this.editor.getLine(cursorPosition.line); 515 | // use our fancy class to figure this out 516 | var th = new ToggleHighlight(); 517 | var result = th.toggleHighlight(lineText, cursorPosition.ch); 518 | // catch up on cursor 519 | var cursorDifference = -2; 520 | if (result.length > lineText.length) { 521 | cursorDifference = 2; 522 | } 523 | this.editor.replaceRange(result, { line: cursorPosition.line, ch: 0 }, { line: cursorPosition.line, ch: lineText.length }); 524 | this.editor.setCursor({ line: cursorPosition.line, ch: cursorPosition.ch + cursorDifference }); 525 | }; 526 | ExtractHighlightsPlugin.prototype.capitalizeFirstLetter = function (s) { 527 | return s.charAt(0).toUpperCase() + s.slice(1); 528 | }; 529 | return ExtractHighlightsPlugin; 530 | }(obsidian.Plugin)); 531 | 532 | module.exports = ExtractHighlightsPlugin; 533 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9FeHRyYWN0SGlnaGxpZ2h0c1BsdWdpblNldHRpbmdzLnRzIiwic3JjL0V4dHJhY3RIaWdobGlnaHRzUGx1Z2luU2V0dGluZ3NUYWIudHMiLCJzcmMvVG9nZ2xlSGlnaGxpZ2h0LnRzIiwic3JjL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyohICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqXHJcbkNvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLlxyXG5cclxuUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55XHJcbnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZC5cclxuXHJcblRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIgQU5EIFRIRSBBVVRIT1IgRElTQ0xBSU1TIEFMTCBXQVJSQU5USUVTIFdJVEhcclxuUkVHQVJEIFRPIFRISVMgU09GVFdBUkUgSU5DTFVESU5HIEFMTCBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZXHJcbkFORCBGSVRORVNTLiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SIEJFIExJQUJMRSBGT1IgQU5ZIFNQRUNJQUwsIERJUkVDVCxcclxuSU5ESVJFQ1QsIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyBPUiBBTlkgREFNQUdFUyBXSEFUU09FVkVSIFJFU1VMVElORyBGUk9NXHJcbkxPU1MgT0YgVVNFLCBEQVRBIE9SIFBST0ZJVFMsIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBORUdMSUdFTkNFIE9SXHJcbk9USEVSIFRPUlRJT1VTIEFDVElPTiwgQVJJU0lORyBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBVU0UgT1JcclxuUEVSRk9STUFOQ0UgT0YgVEhJUyBTT0ZUV0FSRS5cclxuKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiogKi9cclxuLyogZ2xvYmFsIFJlZmxlY3QsIFByb21pc2UgKi9cclxuXHJcbnZhciBleHRlbmRTdGF0aWNzID0gZnVuY3Rpb24oZCwgYikge1xyXG4gICAgZXh0ZW5kU3RhdGljcyA9IE9iamVjdC5zZXRQcm90b3R5cGVPZiB8fFxyXG4gICAgICAgICh7IF9fcHJvdG9fXzogW10gfSBpbnN0YW5jZW9mIEFycmF5ICYmIGZ1bmN0aW9uIChkLCBiKSB7IGQuX19wcm90b19fID0gYjsgfSkgfHxcclxuICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChiLmhhc093blByb3BlcnR5KHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG4gICAgZnVuY3Rpb24gX18oKSB7IHRoaXMuY29uc3RydWN0b3IgPSBkOyB9XHJcbiAgICBkLnByb3RvdHlwZSA9IGIgPT09IG51bGwgPyBPYmplY3QuY3JlYXRlKGIpIDogKF9fLnByb3RvdHlwZSA9IGIucHJvdG90eXBlLCBuZXcgX18oKSk7XHJcbn1cclxuXHJcbmV4cG9ydCB2YXIgX19hc3NpZ24gPSBmdW5jdGlvbigpIHtcclxuICAgIF9fYXNzaWduID0gT2JqZWN0LmFzc2lnbiB8fCBmdW5jdGlvbiBfX2Fzc2lnbih0KSB7XHJcbiAgICAgICAgZm9yICh2YXIgcywgaSA9IDEsIG4gPSBhcmd1bWVudHMubGVuZ3RoOyBpIDwgbjsgaSsrKSB7XHJcbiAgICAgICAgICAgIHMgPSBhcmd1bWVudHNbaV07XHJcbiAgICAgICAgICAgIGZvciAodmFyIHAgaW4gcykgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChzLCBwKSkgdFtwXSA9IHNbcF07XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHJldHVybiB0O1xyXG4gICAgfVxyXG4gICAgcmV0dXJuIF9fYXNzaWduLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX3Jlc3QocywgZSkge1xyXG4gICAgdmFyIHQgPSB7fTtcclxuICAgIGZvciAodmFyIHAgaW4gcykgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChzLCBwKSAmJiBlLmluZGV4T2YocCkgPCAwKVxyXG4gICAgICAgIHRbcF0gPSBzW3BdO1xyXG4gICAgaWYgKHMgIT0gbnVsbCAmJiB0eXBlb2YgT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyA9PT0gXCJmdW5jdGlvblwiKVxyXG4gICAgICAgIGZvciAodmFyIGkgPSAwLCBwID0gT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyhzKTsgaSA8IHAubGVuZ3RoOyBpKyspIHtcclxuICAgICAgICAgICAgaWYgKGUuaW5kZXhPZihwW2ldKSA8IDAgJiYgT2JqZWN0LnByb3RvdHlwZS5wcm9wZXJ0eUlzRW51bWVyYWJsZS5jYWxsKHMsIHBbaV0pKVxyXG4gICAgICAgICAgICAgICAgdFtwW2ldXSA9IHNbcFtpXV07XHJcbiAgICAgICAgfVxyXG4gICAgcmV0dXJuIHQ7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX2RlY29yYXRlKGRlY29yYXRvcnMsIHRhcmdldCwga2V5LCBkZXNjKSB7XHJcbiAgICB2YXIgYyA9IGFyZ3VtZW50cy5sZW5ndGgsIHIgPSBjIDwgMyA/IHRhcmdldCA6IGRlc2MgPT09IG51bGwgPyBkZXNjID0gT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcih0YXJnZXQsIGtleSkgOiBkZXNjLCBkO1xyXG4gICAgaWYgKHR5cGVvZiBSZWZsZWN0ID09PSBcIm9iamVjdFwiICYmIHR5cGVvZiBSZWZsZWN0LmRlY29yYXRlID09PSBcImZ1bmN0aW9uXCIpIHIgPSBSZWZsZWN0LmRlY29yYXRlKGRlY29yYXRvcnMsIHRhcmdldCwga2V5LCBkZXNjKTtcclxuICAgIGVsc2UgZm9yICh2YXIgaSA9IGRlY29yYXRvcnMubGVuZ3RoIC0gMTsgaSA+PSAwOyBpLS0pIGlmIChkID0gZGVjb3JhdG9yc1tpXSkgciA9IChjIDwgMyA/IGQocikgOiBjID4gMyA/IGQodGFyZ2V0LCBrZXksIHIpIDogZCh0YXJnZXQsIGtleSkpIHx8IHI7XHJcbiAgICByZXR1cm4gYyA+IDMgJiYgciAmJiBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBrZXksIHIpLCByO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19wYXJhbShwYXJhbUluZGV4LCBkZWNvcmF0b3IpIHtcclxuICAgIHJldHVybiBmdW5jdGlvbiAodGFyZ2V0LCBrZXkpIHsgZGVjb3JhdG9yKHRhcmdldCwga2V5LCBwYXJhbUluZGV4KTsgfVxyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19tZXRhZGF0YShtZXRhZGF0YUtleSwgbWV0YWRhdGFWYWx1ZSkge1xyXG4gICAgaWYgKHR5cGVvZiBSZWZsZWN0ID09PSBcIm9iamVjdFwiICYmIHR5cGVvZiBSZWZsZWN0Lm1ldGFkYXRhID09PSBcImZ1bmN0aW9uXCIpIHJldHVybiBSZWZsZWN0Lm1ldGFkYXRhKG1ldGFkYXRhS2V5LCBtZXRhZGF0YVZhbHVlKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fYXdhaXRlcih0aGlzQXJnLCBfYXJndW1lbnRzLCBQLCBnZW5lcmF0b3IpIHtcclxuICAgIGZ1bmN0aW9uIGFkb3B0KHZhbHVlKSB7IHJldHVybiB2YWx1ZSBpbnN0YW5jZW9mIFAgPyB2YWx1ZSA6IG5ldyBQKGZ1bmN0aW9uIChyZXNvbHZlKSB7IHJlc29sdmUodmFsdWUpOyB9KTsgfVxyXG4gICAgcmV0dXJuIG5ldyAoUCB8fCAoUCA9IFByb21pc2UpKShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XHJcbiAgICAgICAgZnVuY3Rpb24gZnVsZmlsbGVkKHZhbHVlKSB7IHRyeSB7IHN0ZXAoZ2VuZXJhdG9yLm5leHQodmFsdWUpKTsgfSBjYXRjaCAoZSkgeyByZWplY3QoZSk7IH0gfVxyXG4gICAgICAgIGZ1bmN0aW9uIHJlamVjdGVkKHZhbHVlKSB7IHRyeSB7IHN0ZXAoZ2VuZXJhdG9yW1widGhyb3dcIl0odmFsdWUpKTsgfSBjYXRjaCAoZSkgeyByZWplY3QoZSk7IH0gfVxyXG4gICAgICAgIGZ1bmN0aW9uIHN0ZXAocmVzdWx0KSB7IHJlc3VsdC5kb25lID8gcmVzb2x2ZShyZXN1bHQudmFsdWUpIDogYWRvcHQocmVzdWx0LnZhbHVlKS50aGVuKGZ1bGZpbGxlZCwgcmVqZWN0ZWQpOyB9XHJcbiAgICAgICAgc3RlcCgoZ2VuZXJhdG9yID0gZ2VuZXJhdG9yLmFwcGx5KHRoaXNBcmcsIF9hcmd1bWVudHMgfHwgW10pKS5uZXh0KCkpO1xyXG4gICAgfSk7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX2dlbmVyYXRvcih0aGlzQXJnLCBib2R5KSB7XHJcbiAgICB2YXIgXyA9IHsgbGFiZWw6IDAsIHNlbnQ6IGZ1bmN0aW9uKCkgeyBpZiAodFswXSAmIDEpIHRocm93IHRbMV07IHJldHVybiB0WzFdOyB9LCB0cnlzOiBbXSwgb3BzOiBbXSB9LCBmLCB5LCB0LCBnO1xyXG4gICAgcmV0dXJuIGcgPSB7IG5leHQ6IHZlcmIoMCksIFwidGhyb3dcIjogdmVyYigxKSwgXCJyZXR1cm5cIjogdmVyYigyKSB9LCB0eXBlb2YgU3ltYm9sID09PSBcImZ1bmN0aW9uXCIgJiYgKGdbU3ltYm9sLml0ZXJhdG9yXSA9IGZ1bmN0aW9uKCkgeyByZXR1cm4gdGhpczsgfSksIGc7XHJcbiAgICBmdW5jdGlvbiB2ZXJiKG4pIHsgcmV0dXJuIGZ1bmN0aW9uICh2KSB7IHJldHVybiBzdGVwKFtuLCB2XSk7IH07IH1cclxuICAgIGZ1bmN0aW9uIHN0ZXAob3ApIHtcclxuICAgICAgICBpZiAoZikgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkdlbmVyYXRvciBpcyBhbHJlYWR5IGV4ZWN1dGluZy5cIik7XHJcbiAgICAgICAgd2hpbGUgKF8pIHRyeSB7XHJcbiAgICAgICAgICAgIGlmIChmID0gMSwgeSAmJiAodCA9IG9wWzBdICYgMiA/IHlbXCJyZXR1cm5cIl0gOiBvcFswXSA/IHlbXCJ0aHJvd1wiXSB8fCAoKHQgPSB5W1wicmV0dXJuXCJdKSAmJiB0LmNhbGwoeSksIDApIDogeS5uZXh0KSAmJiAhKHQgPSB0LmNhbGwoeSwgb3BbMV0pKS5kb25lKSByZXR1cm4gdDtcclxuICAgICAgICAgICAgaWYgKHkgPSAwLCB0KSBvcCA9IFtvcFswXSAmIDIsIHQudmFsdWVdO1xyXG4gICAgICAgICAgICBzd2l0Y2ggKG9wWzBdKSB7XHJcbiAgICAgICAgICAgICAgICBjYXNlIDA6IGNhc2UgMTogdCA9IG9wOyBicmVhaztcclxuICAgICAgICAgICAgICAgIGNhc2UgNDogXy5sYWJlbCsrOyByZXR1cm4geyB2YWx1ZTogb3BbMV0sIGRvbmU6IGZhbHNlIH07XHJcbiAgICAgICAgICAgICAgICBjYXNlIDU6IF8ubGFiZWwrKzsgeSA9IG9wWzFdOyBvcCA9IFswXTsgY29udGludWU7XHJcbiAgICAgICAgICAgICAgICBjYXNlIDc6IG9wID0gXy5vcHMucG9wKCk7IF8udHJ5cy5wb3AoKTsgY29udGludWU7XHJcbiAgICAgICAgICAgICAgICBkZWZhdWx0OlxyXG4gICAgICAgICAgICAgICAgICAgIGlmICghKHQgPSBfLnRyeXMsIHQgPSB0Lmxlbmd0aCA+IDAgJiYgdFt0Lmxlbmd0aCAtIDFdKSAmJiAob3BbMF0gPT09IDYgfHwgb3BbMF0gPT09IDIpKSB7IF8gPSAwOyBjb250aW51ZTsgfVxyXG4gICAgICAgICAgICAgICAgICAgIGlmIChvcFswXSA9PT0gMyAmJiAoIXQgfHwgKG9wWzFdID4gdFswXSAmJiBvcFsxXSA8IHRbM10pKSkgeyBfLmxhYmVsID0gb3BbMV07IGJyZWFrOyB9XHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKG9wWzBdID09PSA2ICYmIF8ubGFiZWwgPCB0WzFdKSB7IF8ubGFiZWwgPSB0WzFdOyB0ID0gb3A7IGJyZWFrOyB9XHJcbiAgICAgICAgICAgICAgICAgICAgaWYgKHQgJiYgXy5sYWJlbCA8IHRbMl0pIHsgXy5sYWJlbCA9IHRbMl07IF8ub3BzLnB1c2gob3ApOyBicmVhazsgfVxyXG4gICAgICAgICAgICAgICAgICAgIGlmICh0WzJdKSBfLm9wcy5wb3AoKTtcclxuICAgICAgICAgICAgICAgICAgICBfLnRyeXMucG9wKCk7IGNvbnRpbnVlO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIG9wID0gYm9keS5jYWxsKHRoaXNBcmcsIF8pO1xyXG4gICAgICAgIH0gY2F0Y2ggKGUpIHsgb3AgPSBbNiwgZV07IHkgPSAwOyB9IGZpbmFsbHkgeyBmID0gdCA9IDA7IH1cclxuICAgICAgICBpZiAob3BbMF0gJiA1KSB0aHJvdyBvcFsxXTsgcmV0dXJuIHsgdmFsdWU6IG9wWzBdID8gb3BbMV0gOiB2b2lkIDAsIGRvbmU6IHRydWUgfTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fY3JlYXRlQmluZGluZyhvLCBtLCBrLCBrMikge1xyXG4gICAgaWYgKGsyID09PSB1bmRlZmluZWQpIGsyID0gaztcclxuICAgIG9bazJdID0gbVtrXTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXhwb3J0U3RhcihtLCBleHBvcnRzKSB7XHJcbiAgICBmb3IgKHZhciBwIGluIG0pIGlmIChwICE9PSBcImRlZmF1bHRcIiAmJiAhZXhwb3J0cy5oYXNPd25Qcm9wZXJ0eShwKSkgZXhwb3J0c1twXSA9IG1bcF07XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX3ZhbHVlcyhvKSB7XHJcbiAgICB2YXIgcyA9IHR5cGVvZiBTeW1ib2wgPT09IFwiZnVuY3Rpb25cIiAmJiBTeW1ib2wuaXRlcmF0b3IsIG0gPSBzICYmIG9bc10sIGkgPSAwO1xyXG4gICAgaWYgKG0pIHJldHVybiBtLmNhbGwobyk7XHJcbiAgICBpZiAobyAmJiB0eXBlb2Ygby5sZW5ndGggPT09IFwibnVtYmVyXCIpIHJldHVybiB7XHJcbiAgICAgICAgbmV4dDogZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBpZiAobyAmJiBpID49IG8ubGVuZ3RoKSBvID0gdm9pZCAwO1xyXG4gICAgICAgICAgICByZXR1cm4geyB2YWx1ZTogbyAmJiBvW2krK10sIGRvbmU6ICFvIH07XHJcbiAgICAgICAgfVxyXG4gICAgfTtcclxuICAgIHRocm93IG5ldyBUeXBlRXJyb3IocyA/IFwiT2JqZWN0IGlzIG5vdCBpdGVyYWJsZS5cIiA6IFwiU3ltYm9sLml0ZXJhdG9yIGlzIG5vdCBkZWZpbmVkLlwiKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVhZChvLCBuKSB7XHJcbiAgICB2YXIgbSA9IHR5cGVvZiBTeW1ib2wgPT09IFwiZnVuY3Rpb25cIiAmJiBvW1N5bWJvbC5pdGVyYXRvcl07XHJcbiAgICBpZiAoIW0pIHJldHVybiBvO1xyXG4gICAgdmFyIGkgPSBtLmNhbGwobyksIHIsIGFyID0gW10sIGU7XHJcbiAgICB0cnkge1xyXG4gICAgICAgIHdoaWxlICgobiA9PT0gdm9pZCAwIHx8IG4tLSA+IDApICYmICEociA9IGkubmV4dCgpKS5kb25lKSBhci5wdXNoKHIudmFsdWUpO1xyXG4gICAgfVxyXG4gICAgY2F0Y2ggKGVycm9yKSB7IGUgPSB7IGVycm9yOiBlcnJvciB9OyB9XHJcbiAgICBmaW5hbGx5IHtcclxuICAgICAgICB0cnkge1xyXG4gICAgICAgICAgICBpZiAociAmJiAhci5kb25lICYmIChtID0gaVtcInJldHVyblwiXSkpIG0uY2FsbChpKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgZmluYWxseSB7IGlmIChlKSB0aHJvdyBlLmVycm9yOyB9XHJcbiAgICB9XHJcbiAgICByZXR1cm4gYXI7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX3NwcmVhZCgpIHtcclxuICAgIGZvciAodmFyIGFyID0gW10sIGkgPSAwOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKVxyXG4gICAgICAgIGFyID0gYXIuY29uY2F0KF9fcmVhZChhcmd1bWVudHNbaV0pKTtcclxuICAgIHJldHVybiBhcjtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fc3ByZWFkQXJyYXlzKCkge1xyXG4gICAgZm9yICh2YXIgcyA9IDAsIGkgPSAwLCBpbCA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBpbDsgaSsrKSBzICs9IGFyZ3VtZW50c1tpXS5sZW5ndGg7XHJcbiAgICBmb3IgKHZhciByID0gQXJyYXkocyksIGsgPSAwLCBpID0gMDsgaSA8IGlsOyBpKyspXHJcbiAgICAgICAgZm9yICh2YXIgYSA9IGFyZ3VtZW50c1tpXSwgaiA9IDAsIGpsID0gYS5sZW5ndGg7IGogPCBqbDsgaisrLCBrKyspXHJcbiAgICAgICAgICAgIHJba10gPSBhW2pdO1xyXG4gICAgcmV0dXJuIHI7XHJcbn07XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19hd2FpdCh2KSB7XHJcbiAgICByZXR1cm4gdGhpcyBpbnN0YW5jZW9mIF9fYXdhaXQgPyAodGhpcy52ID0gdiwgdGhpcykgOiBuZXcgX19hd2FpdCh2KTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fYXN5bmNHZW5lcmF0b3IodGhpc0FyZywgX2FyZ3VtZW50cywgZ2VuZXJhdG9yKSB7XHJcbiAgICBpZiAoIVN5bWJvbC5hc3luY0l0ZXJhdG9yKSB0aHJvdyBuZXcgVHlwZUVycm9yKFwiU3ltYm9sLmFzeW5jSXRlcmF0b3IgaXMgbm90IGRlZmluZWQuXCIpO1xyXG4gICAgdmFyIGcgPSBnZW5lcmF0b3IuYXBwbHkodGhpc0FyZywgX2FyZ3VtZW50cyB8fCBbXSksIGksIHEgPSBbXTtcclxuICAgIHJldHVybiBpID0ge30sIHZlcmIoXCJuZXh0XCIpLCB2ZXJiKFwidGhyb3dcIiksIHZlcmIoXCJyZXR1cm5cIiksIGlbU3ltYm9sLmFzeW5jSXRlcmF0b3JdID0gZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpczsgfSwgaTtcclxuICAgIGZ1bmN0aW9uIHZlcmIobikgeyBpZiAoZ1tuXSkgaVtuXSA9IGZ1bmN0aW9uICh2KSB7IHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAoYSwgYikgeyBxLnB1c2goW24sIHYsIGEsIGJdKSA+IDEgfHwgcmVzdW1lKG4sIHYpOyB9KTsgfTsgfVxyXG4gICAgZnVuY3Rpb24gcmVzdW1lKG4sIHYpIHsgdHJ5IHsgc3RlcChnW25dKHYpKTsgfSBjYXRjaCAoZSkgeyBzZXR0bGUocVswXVszXSwgZSk7IH0gfVxyXG4gICAgZnVuY3Rpb24gc3RlcChyKSB7IHIudmFsdWUgaW5zdGFuY2VvZiBfX2F3YWl0ID8gUHJvbWlzZS5yZXNvbHZlKHIudmFsdWUudikudGhlbihmdWxmaWxsLCByZWplY3QpIDogc2V0dGxlKHFbMF1bMl0sIHIpOyB9XHJcbiAgICBmdW5jdGlvbiBmdWxmaWxsKHZhbHVlKSB7IHJlc3VtZShcIm5leHRcIiwgdmFsdWUpOyB9XHJcbiAgICBmdW5jdGlvbiByZWplY3QodmFsdWUpIHsgcmVzdW1lKFwidGhyb3dcIiwgdmFsdWUpOyB9XHJcbiAgICBmdW5jdGlvbiBzZXR0bGUoZiwgdikgeyBpZiAoZih2KSwgcS5zaGlmdCgpLCBxLmxlbmd0aCkgcmVzdW1lKHFbMF1bMF0sIHFbMF1bMV0pOyB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX2FzeW5jRGVsZWdhdG9yKG8pIHtcclxuICAgIHZhciBpLCBwO1xyXG4gICAgcmV0dXJuIGkgPSB7fSwgdmVyYihcIm5leHRcIiksIHZlcmIoXCJ0aHJvd1wiLCBmdW5jdGlvbiAoZSkgeyB0aHJvdyBlOyB9KSwgdmVyYihcInJldHVyblwiKSwgaVtTeW1ib2wuaXRlcmF0b3JdID0gZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpczsgfSwgaTtcclxuICAgIGZ1bmN0aW9uIHZlcmIobiwgZikgeyBpW25dID0gb1tuXSA/IGZ1bmN0aW9uICh2KSB7IHJldHVybiAocCA9ICFwKSA/IHsgdmFsdWU6IF9fYXdhaXQob1tuXSh2KSksIGRvbmU6IG4gPT09IFwicmV0dXJuXCIgfSA6IGYgPyBmKHYpIDogdjsgfSA6IGY7IH1cclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fYXN5bmNWYWx1ZXMobykge1xyXG4gICAgaWYgKCFTeW1ib2wuYXN5bmNJdGVyYXRvcikgdGhyb3cgbmV3IFR5cGVFcnJvcihcIlN5bWJvbC5hc3luY0l0ZXJhdG9yIGlzIG5vdCBkZWZpbmVkLlwiKTtcclxuICAgIHZhciBtID0gb1tTeW1ib2wuYXN5bmNJdGVyYXRvcl0sIGk7XHJcbiAgICByZXR1cm4gbSA/IG0uY2FsbChvKSA6IChvID0gdHlwZW9mIF9fdmFsdWVzID09PSBcImZ1bmN0aW9uXCIgPyBfX3ZhbHVlcyhvKSA6IG9bU3ltYm9sLml0ZXJhdG9yXSgpLCBpID0ge30sIHZlcmIoXCJuZXh0XCIpLCB2ZXJiKFwidGhyb3dcIiksIHZlcmIoXCJyZXR1cm5cIiksIGlbU3ltYm9sLmFzeW5jSXRlcmF0b3JdID0gZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpczsgfSwgaSk7XHJcbiAgICBmdW5jdGlvbiB2ZXJiKG4pIHsgaVtuXSA9IG9bbl0gJiYgZnVuY3Rpb24gKHYpIHsgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uIChyZXNvbHZlLCByZWplY3QpIHsgdiA9IG9bbl0odiksIHNldHRsZShyZXNvbHZlLCByZWplY3QsIHYuZG9uZSwgdi52YWx1ZSk7IH0pOyB9OyB9XHJcbiAgICBmdW5jdGlvbiBzZXR0bGUocmVzb2x2ZSwgcmVqZWN0LCBkLCB2KSB7IFByb21pc2UucmVzb2x2ZSh2KS50aGVuKGZ1bmN0aW9uKHYpIHsgcmVzb2x2ZSh7IHZhbHVlOiB2LCBkb25lOiBkIH0pOyB9LCByZWplY3QpOyB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX21ha2VUZW1wbGF0ZU9iamVjdChjb29rZWQsIHJhdykge1xyXG4gICAgaWYgKE9iamVjdC5kZWZpbmVQcm9wZXJ0eSkgeyBPYmplY3QuZGVmaW5lUHJvcGVydHkoY29va2VkLCBcInJhd1wiLCB7IHZhbHVlOiByYXcgfSk7IH0gZWxzZSB7IGNvb2tlZC5yYXcgPSByYXc7IH1cclxuICAgIHJldHVybiBjb29rZWQ7XHJcbn07XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19pbXBvcnRTdGFyKG1vZCkge1xyXG4gICAgaWYgKG1vZCAmJiBtb2QuX19lc01vZHVsZSkgcmV0dXJuIG1vZDtcclxuICAgIHZhciByZXN1bHQgPSB7fTtcclxuICAgIGlmIChtb2QgIT0gbnVsbCkgZm9yICh2YXIgayBpbiBtb2QpIGlmIChPYmplY3QuaGFzT3duUHJvcGVydHkuY2FsbChtb2QsIGspKSByZXN1bHRba10gPSBtb2Rba107XHJcbiAgICByZXN1bHQuZGVmYXVsdCA9IG1vZDtcclxuICAgIHJldHVybiByZXN1bHQ7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX2ltcG9ydERlZmF1bHQobW9kKSB7XHJcbiAgICByZXR1cm4gKG1vZCAmJiBtb2QuX19lc01vZHVsZSkgPyBtb2QgOiB7IGRlZmF1bHQ6IG1vZCB9O1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19jbGFzc1ByaXZhdGVGaWVsZEdldChyZWNlaXZlciwgcHJpdmF0ZU1hcCkge1xyXG4gICAgaWYgKCFwcml2YXRlTWFwLmhhcyhyZWNlaXZlcikpIHtcclxuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiYXR0ZW1wdGVkIHRvIGdldCBwcml2YXRlIGZpZWxkIG9uIG5vbi1pbnN0YW5jZVwiKTtcclxuICAgIH1cclxuICAgIHJldHVybiBwcml2YXRlTWFwLmdldChyZWNlaXZlcik7XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBfX2NsYXNzUHJpdmF0ZUZpZWxkU2V0KHJlY2VpdmVyLCBwcml2YXRlTWFwLCB2YWx1ZSkge1xyXG4gICAgaWYgKCFwcml2YXRlTWFwLmhhcyhyZWNlaXZlcikpIHtcclxuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiYXR0ZW1wdGVkIHRvIHNldCBwcml2YXRlIGZpZWxkIG9uIG5vbi1pbnN0YW5jZVwiKTtcclxuICAgIH1cclxuICAgIHByaXZhdGVNYXAuc2V0KHJlY2VpdmVyLCB2YWx1ZSk7XHJcbiAgICByZXR1cm4gdmFsdWU7XHJcbn1cclxuIiwiZXhwb3J0IGRlZmF1bHQgY2xhc3MgRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW5TZXR0aW5ncyB7XG4gIHB1YmxpYyBoZWFkbGluZVRleHQ6IHN0cmluZztcbiAgcHVibGljIGFkZEZvb3Rub3RlczogYm9vbGVhbjtcbiAgcHVibGljIHVzZUJvbGRGb3JIaWdobGlnaHRzOiBib29sZWFuO1xuICBwdWJsaWMgY3JlYXRlTGlua3M6IGJvb2xlYW47XG4gIHB1YmxpYyBhdXRvQ2FwaXRhbGl6ZTogYm9vbGVhbjtcbiAgcHVibGljIGNyZWF0ZU5ld0ZpbGU6IGJvb2xlYW47XG4gIHB1YmxpYyBleHBsb2RlSW50b05vdGVzOiBib29sZWFuO1xuICBwdWJsaWMgb3BlbkV4cGxvZGVkTm90ZXM6IGJvb2xlYW47XG4gIHB1YmxpYyBjcmVhdGVDb250ZXh0dWFsUXVvdGVzOiBib29sZWFuO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuaGVhZGxpbmVUZXh0ID0gXCJcIjtcbiAgICB0aGlzLmFkZEZvb3Rub3RlcyA9IGZhbHNlO1xuICAgIHRoaXMudXNlQm9sZEZvckhpZ2hsaWdodHMgPSBmYWxzZTtcbiAgICB0aGlzLmNyZWF0ZUxpbmtzID0gZmFsc2U7XG4gICAgdGhpcy5hdXRvQ2FwaXRhbGl6ZSA9IGZhbHNlO1xuICAgIHRoaXMuY3JlYXRlTmV3RmlsZSA9IGZhbHNlO1xuICAgIHRoaXMuZXhwbG9kZUludG9Ob3RlcyA9IGZhbHNlO1xuICAgIHRoaXMub3BlbkV4cGxvZGVkTm90ZXMgPSBmYWxzZTtcbiAgICB0aGlzLmNyZWF0ZUNvbnRleHR1YWxRdW90ZXMgPSBmYWxzZTtcbiAgfVxufVxuIiwiaW1wb3J0IHtBcHAsIFBsdWdpblNldHRpbmdUYWIsIFNldHRpbmd9IGZyb20gXCJvYnNpZGlhblwiO1xuaW1wb3J0IEV4dHJhY3RIaWdobGlnaHRzUGx1Z2luIGZyb20gXCIuL21haW5cIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW5TZXR0aW5nc1RhYiBleHRlbmRzIFBsdWdpblNldHRpbmdUYWIge1xuXHRwcml2YXRlIHJlYWRvbmx5IHBsdWdpbjogRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW47XG5cblx0Y29uc3RydWN0b3IoYXBwOiBBcHAsIHBsdWdpbjogRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW4pIHtcblx0XHRzdXBlcihhcHAsIHBsdWdpbik7XG5cdFx0dGhpcy5wbHVnaW4gPSBwbHVnaW47XG5cdH1cblxuXHRkaXNwbGF5KCk6IHZvaWQge1xuXHRcdGNvbnN0IHtjb250YWluZXJFbH0gPSB0aGlzO1xuXG5cdFx0Y29udGFpbmVyRWwuZW1wdHkoKTtcblxuXHRcdGNvbnRhaW5lckVsLmNyZWF0ZUVsKFwiaDJcIiwge3RleHQ6IFwiRXh0cmFjdCBIaWdobGlnaHRzIFBsdWdpblwifSk7XG5cblx0XHRuZXcgU2V0dGluZyhjb250YWluZXJFbClcblx0XHRcdC5zZXROYW1lKFwiSGVhZGluZyBUZXh0XCIpXG5cdFx0XHQuc2V0RGVzYyhcIklmIHNldCwgd2lsbCBhZGQgYCMjIFlvdXIgVGV4dGAuIFVzZSAkTk9URV9USVRMRSB0byBpbmNsdWRlIHRpdGxlLiBMZWF2ZSBibGFuayB0byBvbWl0LiBcIilcblx0XHRcdC5hZGRUZXh0KCh0ZXh0KSA9PlxuXHRcdFx0XHR0ZXh0XG5cdFx0XHRcdFx0LnNldFBsYWNlaG9sZGVyKFwiSGlnaGxpZ2h0cyBmb3IgJE5PVEVfVElUTEVcIilcblx0XHRcdFx0XHQuc2V0VmFsdWUodGhpcy5wbHVnaW4uc2V0dGluZ3MuaGVhZGxpbmVUZXh0KVxuXHRcdFx0XHRcdC5vbkNoYW5nZSgodmFsdWUpID0+IHtcblx0XHRcdFx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLmhlYWRsaW5lVGV4dCA9IHZhbHVlO1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2F2ZURhdGEodGhpcy5wbHVnaW4uc2V0dGluZ3MpO1xuXHRcdFx0XHRcdH0pXG5cdFx0XHQpO1xuXG5cdFx0bmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0XHQuc2V0TmFtZSgnVXNlIGJvbGQgZm9yIGhpZ2hsaWdodHMnKVxuXHRcdFx0LnNldERlc2MoXG5cdFx0XHRcdCdJZiBlbmFibGVkLCB3aWxsIGluY2x1ZGUgY2xhc3NpYyBtYXJrZG93biBib2xkICgqKikgc2VjdGlvbnMgYXMgaGlnaGxpZ2h0cycsXG5cdFx0XHQpXG5cdFx0XHQuYWRkVG9nZ2xlKCh0b2dnbGUpID0+XG5cdFx0XHRcdHRvZ2dsZS5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy51c2VCb2xkRm9ySGlnaGxpZ2h0cykub25DaGFuZ2UoKHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MudXNlQm9sZEZvckhpZ2hsaWdodHMgPSB2YWx1ZTtcblx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zYXZlRGF0YSh0aGlzLnBsdWdpbi5zZXR0aW5ncyk7XG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXG5cdFx0bmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0XHQuc2V0TmFtZSgnRW5hYmxlIEZvb3Rub3RlcycpXG5cdFx0XHQuc2V0RGVzYyhcblx0XHRcdFx0J0lmIGVuYWJsZWQsIHdpbGwgYWRkIGEgZm9vdG5vdGUgdG8gdGhlIGN1cnJlbnQgZG9jdW1lbnQgdG8gZWFjaCBoaWdobGlnaHQgaW4geW91ciBsaXN0LiBVc2VmdWwgd2hlbiB5b3Ugd2FuIHRvIGtlZXAgdHJhY2sgb2Ygd2hpY2ggaGlnaGxpZ2h0IGNhbWUgZnJvbSB3aGljaCBzb3VyY2UgZmlsZS4nLFxuXHRcdFx0KVxuXHRcdFx0LmFkZFRvZ2dsZSgodG9nZ2xlKSA9PlxuXHRcdFx0XHR0b2dnbGUuc2V0VmFsdWUodGhpcy5wbHVnaW4uc2V0dGluZ3MuYWRkRm9vdG5vdGVzKS5vbkNoYW5nZSgodmFsdWUpID0+IHtcblx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5hZGRGb290bm90ZXMgPSB2YWx1ZTtcblx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zYXZlRGF0YSh0aGlzLnBsdWdpbi5zZXR0aW5ncyk7XG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdFx0LnNldE5hbWUoJ0F1dG8tY2FwaXRhbGl6ZSBmaXJzdCBsZXR0ZXInKVxuXHRcdFx0LnNldERlc2MoXG5cdFx0XHRcdCdJZiBlbmFibGVkLCBjYXBpdGFsaXplcyB0aGUgZmlyc3QgbGV0dGVyIG9mIGVhY2ggaGlnaGxpZ2h0LicsXG5cdFx0XHQpXG5cdFx0XHQuYWRkVG9nZ2xlKCh0b2dnbGUpID0+XG5cdFx0XHRcdHRvZ2dsZS5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy5hdXRvQ2FwaXRhbGl6ZSkub25DaGFuZ2UoKHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuYXV0b0NhcGl0YWxpemUgPSB2YWx1ZTtcblx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zYXZlRGF0YSh0aGlzLnBsdWdpbi5zZXR0aW5ncyk7XG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXG5cdFx0bmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0XHQuc2V0TmFtZSgnQ3JlYXRlIGxpbmtzJylcblx0XHRcdC5zZXREZXNjKFxuXHRcdFx0XHQnSWYgZW5hYmxlZCwgd2lsbCB0dXJuIGVhY2ggaGlnaGxpZ2h0IGludG8gYSBbWyBsaW5rIF1dIHRvIGNyZWF0ZSBhIGhpZ2hsaWdodCBNT0MnLFxuXHRcdFx0KVxuXHRcdFx0LmFkZFRvZ2dsZSgodG9nZ2xlKSA9PlxuXHRcdFx0XHR0b2dnbGUuc2V0VmFsdWUodGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlTGlua3MpLm9uQ2hhbmdlKCh2YWx1ZSkgPT4ge1xuXHRcdFx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLmNyZWF0ZUxpbmtzID0gdmFsdWU7XG5cblx0XHRcdFx0XHQvLyBkaXNhYmxlIGV4cGxvZGUgbm90ZXMgbW9kZVxuXHRcdFx0XHRcdGlmKHRoaXMucGx1Z2luLnNldHRpbmdzLmV4cGxvZGVJbnRvTm90ZXMgJiYgdmFsdWUgPT0gZmFsc2UpIHtcblx0XHRcdFx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLmV4cGxvZGVJbnRvTm90ZXMgPSBmYWxzZTtcblx0XHRcdFx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLm9wZW5FeHBsb2RlZE5vdGVzID0gZmFsc2U7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2F2ZURhdGEodGhpcy5wbHVnaW4uc2V0dGluZ3MpO1xuXHRcdFx0XHR9KSxcblx0XHRcdCk7XG5cblx0XHRuZXcgU2V0dGluZyhjb250YWluZXJFbClcblx0XHRcdC5zZXROYW1lKCdPcGVuIG5ldyBmaWxlIHdpdGggaGlnaGxpZ2h0cycpXG5cdFx0XHQuc2V0RGVzYyhcblx0XHRcdFx0J0lmIGVuYWJsZWQsIG9wZW5zIGEgbmV3IGZpbGUgd2l0aCB0aGUgaGlnaGxpZ2h0cyBjb3BpZWQgaW50by4nLFxuXHRcdFx0KVxuXHRcdFx0LmFkZFRvZ2dsZSgodG9nZ2xlKSA9PlxuXHRcdFx0XHR0b2dnbGUuc2V0VmFsdWUodGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlTmV3RmlsZSkub25DaGFuZ2UoKHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlTmV3RmlsZSA9IHZhbHVlO1xuXG5cdFx0XHRcdFx0Ly8gZGlzYWJsZSBleHBsb2RlIG5vdGVzIG1vZGVcblx0XHRcdFx0XHRpZih0aGlzLnBsdWdpbi5zZXR0aW5ncy5leHBsb2RlSW50b05vdGVzICYmIHZhbHVlID09IGZhbHNlKSB7XG5cdFx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5leHBsb2RlSW50b05vdGVzID0gZmFsc2U7XG5cdFx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5vcGVuRXhwbG9kZWROb3RlcyA9IGZhbHNlO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHRoaXMucGx1Z2luLnNhdmVEYXRhKHRoaXMucGx1Z2luLnNldHRpbmdzKTtcblx0XHRcdFx0fSksXG5cdFx0XHQpO1xuXG5cdFx0Y29udGFpbmVyRWwuY3JlYXRlRWwoXCJoMlwiLCB7dGV4dDogXCLwn5KlIEV4cGxvZGUgTm90ZXMgTW9kZSDwn5KlXCJ9KTtcblx0XHRjb250YWluZXJFbC5jcmVhdGVFbChcInBcIiwge3RleHQ6IFwiQSBzZWNyZXQgbW9kZSB0aGF0IHdpbGwgdGFrZSB5b3VyIGhpZ2hsaWdodGluZyB0byB0aGUgbmV4dCBsZXZlbC4gT25seSBhdmFpbGFibGUgaWYgeW91IGhhdmUgICdDcmVhdGUgTGlua3MnIGFuZCAnQ3JlYXRlIG5ldyBGaWxlJyBlbmFibGVkLiBBZnRlciBlbmFibGluZyBib3RoLCBjbG9zZSB0aGlzIHdpbmRvdyBhbmQgb3BlbiBhZ2FpbiB0byBzZWUgb3B0aW9ucy5cIn0pO1xuXG5cdFx0aWYodGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlTGlua3MgJiYgdGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlTmV3RmlsZSkge1xuXHRcdFx0bmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0XHRcdC5zZXROYW1lKCdFeHBsb2RlIGxpbmtzIGludG8gbm90ZXMnKVxuXHRcdFx0XHQuc2V0RGVzYyhcblx0XHRcdFx0XHQnSWYgZW5hYmxlZCwgd2lsbCB0dXJuIGVhY2ggaGlnaGxpZ2h0IGludG8gYSBub3RlIHdpdGggdGhlIGhpZ2hsaWdodGVkIHRleHQgYXMgcXVvdGUgYW5kIGEgYmFja2xpbmsgdG8gdGhlIE1PQyBhbmQgc291cmNlLWZpbGUuIFZlcnkgcG93ZXJmdWwgYnV0IHVzZSB3aXRoIGNhdXRpb24hJyxcblx0XHRcdFx0KVxuXHRcdFx0XHQuYWRkVG9nZ2xlKCh0b2dnbGUpID0+XG5cdFx0XHRcdFx0dG9nZ2xlLnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLmV4cGxvZGVJbnRvTm90ZXMpLm9uQ2hhbmdlKCh2YWx1ZSkgPT4ge1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuZXhwbG9kZUludG9Ob3RlcyA9IHZhbHVlO1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2F2ZURhdGEodGhpcy5wbHVnaW4uc2V0dGluZ3MpO1xuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHQpO1xuXG5cdFx0XHRuZXcgU2V0dGluZyhjb250YWluZXJFbClcblx0XHRcdFx0LnNldE5hbWUoJ09wZW4gZXhwbG9kZWQgbm90ZXMgb24gY3JlYXRpb24nKVxuXHRcdFx0XHQuc2V0RGVzYyhcblx0XHRcdFx0XHQnSWYgZW5hYmxlZCwgd2lsbCBvcGVuIGVhY2ggb2YgeW91ciBleHBsb2RlZCBub3RlcyB3aGVuIHlvdSBjcmVhdGUgdGhlbS4gRnVuIGFuZCB1c2VmdWwgdG8gY29udGludWUgd29ya2luZyBpbiB5b3VyIGhpZ2hsaWdodC1ub3RlcyByaWdodCBhd2F5IScsXG5cdFx0XHRcdClcblx0XHRcdFx0LmFkZFRvZ2dsZSgodG9nZ2xlKSA9PlxuXHRcdFx0XHRcdHRvZ2dsZS5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy5vcGVuRXhwbG9kZWROb3Rlcykub25DaGFuZ2UoKHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5vcGVuRXhwbG9kZWROb3RlcyA9IHZhbHVlO1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2F2ZURhdGEodGhpcy5wbHVnaW4uc2V0dGluZ3MpO1xuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHQpO1xuXG5cdFx0XHRuZXcgU2V0dGluZyhjb250YWluZXJFbClcblx0XHRcdFx0LnNldE5hbWUoJ0NyZWF0ZSBjb250ZXh0dWFsIHF1b3RlcycpXG5cdFx0XHRcdC5zZXREZXNjKFxuXHRcdFx0XHRcdCdJZiBlbmFibGVkLCB3aWxsIHF1b3RlIHRoZSBmdWxsIGxpbmUgb2YgeW91ciBoaWdobGlnaHQsIG5vdCBqdXN0IHRoZSBoaWdobGlnaHQgaXRzZWxmLiBVc2VmdWwgZm9yIGtlZXBpbmcgdGhlIGNvbnRleHQgb2YgeW91ciBoaWdobGlnaHQuJyxcblx0XHRcdFx0KVxuXHRcdFx0XHQuYWRkVG9nZ2xlKCh0b2dnbGUpID0+XG5cdFx0XHRcdFx0dG9nZ2xlLnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLmNyZWF0ZUNvbnRleHR1YWxRdW90ZXMpLm9uQ2hhbmdlKCh2YWx1ZSkgPT4ge1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuY3JlYXRlQ29udGV4dHVhbFF1b3RlcyA9IHZhbHVlO1xuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2F2ZURhdGEodGhpcy5wbHVnaW4uc2V0dGluZ3MpO1xuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHQpO1xuXG5cdFx0fVxuXG5cdH1cbn0iLCJleHBvcnQgZGVmYXVsdCBjbGFzcyBUb2dnbGVIaWdobGlnaHQge1xuXG4gICAgdG9nZ2xlSGlnaGxpZ2h0KHM6IHN0cmluZywgY2g/OiBudW1iZXIpIHtcbiAgICAgICAgaWYocyA9PSBcIlwiKSByZXR1cm4gXCJcIjtcbiAgICAgICAgaWYocy5pbmRleE9mKFwiLlwiKSA8IDApIHsgcmV0dXJuIFwiPT1cIiArIHMgKyBcIj09XCJ9XG5cbiAgICAgICAgbGV0IGxlZnQgPSBzLnN1YnN0cmluZygwLCBjaCk7XG4gICAgICAgIGxldCByaWdodCA9IHMuc3Vic3RyaW5nKGNoKTtcbiAgICAgICAgbGV0IG1hcmtlZCA9IGxlZnQgKyBcIiRDVVJTT1IkXCIgKyByaWdodDtcblxuICAgICAgICAvLyBodHRwczovL3JlZ2V4MTAxLmNvbS9yL0JTcHZWNi83XG4gICAgICAgIC8vIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vYS81NTUzOTI0XG4gICAgICAgIGxldCBwID0gbWFya2VkLm1hdGNoKC8oPT0oLio/KT09KXxbXi4hP1xcc11bXi4hP10qKD86Wy4hP10oPyFbJ1wiXT9cXHN8JClbXi4hP10qKSpbLiE/XT9bJ1wiXT8oPz1cXHN8JCkvZ20pO1xuXG4gICAgICAgIGxldCBucCA9IG5ldyBBcnJheSgpO1xuXG4gICAgICAgIGlmKHAubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgcC5mb3JFYWNoKGZ1bmN0aW9uIChwYXJ0KSB7XG4gICAgICAgICAgICAgICAgaWYodHlwZW9mIHBhcnQgIT09ICd1bmRlZmluZWQnICkge1xuICAgICAgICAgICAgICAgICAgICBpZihwYXJ0LnRyaW0oKSA9PSBcIlwiKSB7ICByZXR1cm47IH1cblxuICAgICAgICAgICAgICAgICAgICBpZihwYXJ0LmluY2x1ZGVzKFwiJENVUlNPUiRcIikpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYocGFydC5zdGFydHNXaXRoKFwiPT1cIikgJiYgcGFydC5lbmRzV2l0aChcIj09XCIpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFydCA9IHBhcnQucmVwbGFjZSgvPT0vZywgXCJcIik7XG4gICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcnQgPSBcIj09XCIgKyBwYXJ0ICsgXCI9PVwiO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgcGFydCA9IHBhcnQucmVwbGFjZShcIiRDVVJTT1IkXCIsIFwiXCIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFydCA9IHBhcnQudHJpbSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIHBhcnQgPSBwYXJ0LnRyaW0oKTtcbiAgICAgICAgICAgICAgICAgICAgbnAucHVzaChwYXJ0KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgcmV0dXJuIG5wLmpvaW4oXCIgXCIpO1xuICAgICAgICB9XG4gICAgfVxufSIsImltcG9ydCB7UGx1Z2luLCBOb3RpY2UsIGFkZEljb24sIFZpZXcsIE1hcmtkb3duVmlldywgV29ya3NwYWNlfSBmcm9tIFwib2JzaWRpYW5cIlxuaW1wb3J0IEV4dHJhY3RIaWdobGlnaHRzUGx1Z2luU2V0dGluZ3MgZnJvbSBcIi4vRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW5TZXR0aW5nc1wiXG5pbXBvcnQgRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW5TZXR0aW5nc1RhYiBmcm9tIFwiLi9FeHRyYWN0SGlnaGxpZ2h0c1BsdWdpblNldHRpbmdzVGFiXCJcbmltcG9ydCBUb2dnbGVIaWdobGlnaHQgZnJvbSBcIi4vVG9nZ2xlSGlnaGxpZ2h0XCI7XG5cbmFkZEljb24oJ3RhcmdldCcsICc8cGF0aCBkPVwiTTUwIDg4QzI5LjAxMzIgODggMTIgNzAuOTg2OCAxMiA1MEMxMiAyOS4wMTMyIDI5LjAxMzIgMTIgNTAgMTJDNzAuOTg2OCAxMiA4OCAyOS4wMTMyIDg4IDUwQzg3Ljk3NjEgNzAuOTc2OSA3MC45NzY5IDg3Ljk3NjEgNTAgODhaTTUwIDIyLjg1NzFDMzUuMDA5NCAyMi44NTcxIDIyLjg1NzEgMzUuMDA5NCAyMi44NTcxIDUwQzIyLjg1NzEgNjQuOTkwNiAzNS4wMDk0IDc3LjE0MjkgNTAgNzcuMTQyOUM2NC45OTA2IDc3LjE0MjkgNzcuMTQyOSA2NC45OTA2IDc3LjE0MjkgNTBDNzcuMTQyOSAzNS4wMDk0IDY0Ljk5MDYgMjIuODU3MSA1MCAyMi44NTcxWk01MCA2Ni4yODU3QzQxLjAwNTYgNjYuMjg1NyAzMy43MTQzIDU4Ljk5NDMgMzMuNzE0MyA1MEMzMy43MTQzIDQxLjAwNTYgNDEuMDA1NiAzMy43MTQzIDUwIDMzLjcxNDNDNTguOTk0MyAzMy43MTQzIDY2LjI4NTcgNDEuMDA1NiA2Ni4yODU3IDUwQzY2LjI4NTcgNTQuMzE5MiA2NC41Njk5IDU4LjQ2MTYgNjEuNTE1NyA2MS41MTU3QzU4LjQ2MTYgNjQuNTY5OSA1NC4zMTkyIDY2LjI4NTcgNTAgNjYuMjg1N1pcIiBmaWxsPVwiIzY0NjQ2NFwiLz4nKVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBFeHRyYWN0SGlnaGxpZ2h0c1BsdWdpbiBleHRlbmRzIFBsdWdpbiB7XG5cblx0cHVibGljIHNldHRpbmdzOiBFeHRyYWN0SGlnaGxpZ2h0c1BsdWdpblNldHRpbmdzO1xuXHRwdWJsaWMgc3RhdHVzQmFyOiBIVE1MRWxlbWVudFxuXHRwdWJsaWMgY291bnRlcjogMDtcblx0cHJpdmF0ZSBlZGl0b3I6IENvZGVNaXJyb3IuRWRpdG9yO1xuXG5cdGFzeW5jIG9ubG9hZCgpIHtcblx0XHR0aGlzLmNvdW50ZXIgPSAwO1xuXHRcdHRoaXMubG9hZFNldHRpbmdzKCk7XG5cdFx0dGhpcy5hZGRTZXR0aW5nVGFiKG5ldyBFeHRyYWN0SGlnaGxpZ2h0c1BsdWdpblNldHRpbmdzVGFiKHRoaXMuYXBwLCB0aGlzKSk7XG5cblx0XHR0aGlzLnN0YXR1c0JhciA9IHRoaXMuYWRkU3RhdHVzQmFySXRlbSgpXG5cblx0XHR0aGlzLmFkZFJpYmJvbkljb24oJ3RhcmdldCcsICdFeHRyYWN0IEhpZ2hsaWdodHMnLCAoKSA9PiB7XG5cdFx0XHR0aGlzLmV4dHJhY3RIaWdobGlnaHRzKCk7XG5cdFx0fSk7XG5cblx0XHR0aGlzLmFkZENvbW1hbmQoe1xuXHRcdFx0aWQ6IFwic2hvcnRjdXQtZXh0cmFjdC1oaWdobGlnaHRzXCIsXG5cdFx0XHRuYW1lOiBcIlNob3J0Y3V0IGZvciBleHRyYWN0aW5nIGhpZ2hsaWdodHNcIixcblx0XHRcdGNhbGxiYWNrOiAoKSA9PiB0aGlzLmV4dHJhY3RIaWdobGlnaHRzKCksXG5cdFx0XHRob3RrZXlzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHRtb2RpZmllcnM6IFtcIkFsdFwiLCBcIlNoaWZ0XCJdLFxuXHRcdFx0XHRcdGtleTogXCLCsVwiLFxuXHRcdFx0XHR9LFxuXHRcdFx0XSxcblx0XHR9KTtcblxuXHRcdHRoaXMuYWRkQ29tbWFuZCh7XG5cdFx0XHRpZDogXCJzaG9ydGN1dC1oaWdobGlnaHQtc2VudGVuY2VcIixcblx0XHRcdG5hbWU6IFwiU2hvcnRjdXQgZm9yIGhpZ2hsaWdodGluZyBzZW50ZW5jZSBjdXJzb3IgaXMgaW5cIixcblx0XHRcdGNhbGxiYWNrOiAoKSA9PiB0aGlzLmNyZWF0ZUhpZ2hsaWdodCgpLFxuXHRcdFx0aG90a2V5czogW1xuXHRcdFx0XHR7XG5cdFx0XHRcdFx0bW9kaWZpZXJzOiBbXCJBbHRcIiwgXCJTaGlmdFwiXSxcblx0XHRcdFx0XHRrZXk6IFwi4oCUXCIsXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0pO1xuXHR9XG5cblx0bG9hZFNldHRpbmdzKCkge1xuXHRcdHRoaXMuc2V0dGluZ3MgPSBuZXcgRXh0cmFjdEhpZ2hsaWdodHNQbHVnaW5TZXR0aW5ncygpO1xuXHRcdChhc3luYyAoKSA9PiB7XG5cdFx0ICBjb25zdCBsb2FkZWRTZXR0aW5ncyA9IGF3YWl0IHRoaXMubG9hZERhdGEoKTtcblx0XHQgIGlmIChsb2FkZWRTZXR0aW5ncykge1xuXHRcdFx0Ly8gY29uc29sZS5sb2coXCJGb3VuZCBleGlzdGluZyBzZXR0aW5ncyBmaWxlXCIpO1xuXHRcdFx0dGhpcy5zZXR0aW5ncy5oZWFkbGluZVRleHQgPSBsb2FkZWRTZXR0aW5ncy5oZWFkbGluZVRleHQ7XG5cdFx0XHR0aGlzLnNldHRpbmdzLmFkZEZvb3Rub3RlcyA9IGxvYWRlZFNldHRpbmdzLmFkZEZvb3Rub3Rlcztcblx0XHRcdHRoaXMuc2V0dGluZ3MuY3JlYXRlTGlua3MgPSBsb2FkZWRTZXR0aW5ncy5jcmVhdGVMaW5rcztcblx0XHRcdHRoaXMuc2V0dGluZ3MuYXV0b0NhcGl0YWxpemUgPSBsb2FkZWRTZXR0aW5ncy5hdXRvQ2FwaXRhbGl6ZTtcblx0XHRcdHRoaXMuc2V0dGluZ3MuY3JlYXRlTmV3RmlsZSA9IGxvYWRlZFNldHRpbmdzLmNyZWF0ZU5ld0ZpbGU7XG5cdFx0XHR0aGlzLnNldHRpbmdzLmV4cGxvZGVJbnRvTm90ZXMgPSBsb2FkZWRTZXR0aW5ncy5leHBsb2RlSW50b05vdGVzO1xuXHRcdFx0dGhpcy5zZXR0aW5ncy5vcGVuRXhwbG9kZWROb3RlcyA9IGxvYWRlZFNldHRpbmdzLm9wZW5FeHBsb2RlZE5vdGVzO1xuXHRcdFx0dGhpcy5zZXR0aW5ncy5jcmVhdGVDb250ZXh0dWFsUXVvdGVzID0gbG9hZGVkU2V0dGluZ3MuY3JlYXRlQ29udGV4dHVhbFF1b3Rlcztcblx0XHQgIH0gZWxzZSB7XG5cdFx0XHQvLyBjb25zb2xlLmxvZyhcIk5vIHNldHRpbmdzIGZpbGUgZm91bmQsIHNhdmluZy4uLlwiKTtcblx0XHRcdHRoaXMuc2F2ZURhdGEodGhpcy5zZXR0aW5ncyk7XG5cdFx0ICB9XG5cdFx0fSkoKTtcblx0fVxuXG5cdGFzeW5jIGV4dHJhY3RIaWdobGlnaHRzKCkge1xuXHRcdGxldCBhY3RpdmVMZWFmOiBhbnkgPSB0aGlzLmFwcC53b3Jrc3BhY2UuYWN0aXZlTGVhZiA/PyBudWxsXG5cblx0XHRsZXQgbmFtZSA9IGFjdGl2ZUxlYWY/LnZpZXcuZmlsZS5iYXNlbmFtZTtcblxuXHRcdHRyeSB7XG5cdFx0XHRpZiAoYWN0aXZlTGVhZj8udmlldz8uZGF0YSkge1xuXHRcdFx0XHRsZXQgcHJvY2Vzc1Jlc3VsdHMgPSB0aGlzLnByb2Nlc3NIaWdobGlnaHRzKGFjdGl2ZUxlYWYudmlldyk7XG5cdFx0XHRcdGxldCBoaWdobGlnaHRzVGV4dCA9IHByb2Nlc3NSZXN1bHRzLm1hcmtkb3duO1xuXHRcdFx0XHRsZXQgaGlnaGxpZ2h0cyA9IHByb2Nlc3NSZXN1bHRzLmhpZ2hsaWdodHM7XG5cdFx0XHRcdGxldCBiYXNlTmFtZXMgPSBwcm9jZXNzUmVzdWx0cy5iYXNlTmFtZXM7XG5cdFx0XHRcdGxldCBjb250ZXh0cyA9IHByb2Nlc3NSZXN1bHRzLmNvbnRleHRzO1xuXHRcdFx0XHRsZXQgc2F2ZVN0YXR1cyA9IHRoaXMuc2F2ZVRvQ2xpcGJvYXJkKGhpZ2hsaWdodHNUZXh0KTtcblx0XHRcdFx0bmV3IE5vdGljZShzYXZlU3RhdHVzKTtcblxuXHRcdFx0XHRjb25zdCBuZXdCYXNlbmFtZU1PQyA9IFwiSGlnaGxpZ2h0cyBmb3IgXCIgKyBuYW1lICsgXCIubWRcIjtcblx0XHRcdFx0aWYgKHRoaXMuc2V0dGluZ3MuY3JlYXRlTmV3RmlsZSkge1xuXHRcdFx0XHRcdC8vIEFkZCBsaW5rIGJhY2sgdG8gT3JpZ2luYWxcblx0XHRcdFx0XHRoaWdobGlnaHRzVGV4dCArPSBgIyMgU291cmNlXFxuLSBbWyR7bmFtZX1dXWA7XG5cblx0XHRcdFx0XHRhd2FpdCB0aGlzLnNhdmVUb0ZpbGUobmV3QmFzZW5hbWVNT0MsIGhpZ2hsaWdodHNUZXh0KTtcblx0XHRcdFx0XHRhd2FpdCB0aGlzLmFwcC53b3Jrc3BhY2Uub3BlbkxpbmtUZXh0KG5ld0Jhc2VuYW1lTU9DLCBuZXdCYXNlbmFtZU1PQywgdHJ1ZSk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpZih0aGlzLnNldHRpbmdzLmNyZWF0ZU5ld0ZpbGUgJiYgdGhpcy5zZXR0aW5ncy5jcmVhdGVMaW5rcyAmJiB0aGlzLnNldHRpbmdzLmV4cGxvZGVJbnRvTm90ZXMpIHtcblx0XHRcdFx0XHRmb3IodmFyIGkgPSAwOyBpIDwgYmFzZU5hbWVzLmxlbmd0aDsgaSsrKSB7XG5cdFx0XHRcdFx0XHQvLyBjb25zb2xlLmxvZyhcIkNyZWF0aW5nIGZpbGUgZm9yIFwiICsgYmFzZU5hbWVzW2ldKTtcblx0XHRcdFx0XHRcdHZhciBjb250ZW50ID0gXCJcIjtcblx0XHRcdFx0XHRcdC8vIGFkZCBoaWdobGlnaHQgYXMgcXVvdGVcblx0XHRcdFx0XHRcdGNvbnRlbnQgKz0gXCIjIyBTb3VyY2VcXG5cIlxuXHRcdFx0XHRcdFx0aWYodGhpcy5zZXR0aW5ncy5jcmVhdGVDb250ZXh0dWFsUXVvdGVzKSB7XG5cdFx0XHRcdFx0XHRcdC8vIGNvbnRleHQgcXVvdGVcblx0XHRcdFx0XHRcdFx0Y29udGVudCArPSBgPiAke2NvbnRleHRzW2ldfVteMV1gO1xuXHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0Ly8gcmVndWxhciBoaWdobGlnaHQgcXVvdGVcblx0XHRcdFx0XHRcdFx0Y29udGVudCArPSBgPiAke2hpZ2hsaWdodHNbaV19W14xXWA7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRjb250ZW50ICs9IFwiXFxuXFxuXCI7XG5cdFx0XHRcdFx0XHRjb250ZW50ICs9IGBbXjFdOiBbWyR7bmFtZX1dXWA7XG5cdFx0XHRcdFx0XHRjb250ZW50ICs9IFwiXFxuXCI7XG5cdFx0XHRcdFx0XHQvLyBjb25zb2xlLmxvZyhjb250ZW50KTtcblxuXHRcdFx0XHRcdFx0Y29uc3QgbmV3QmFzZW5hbWUgPSBiYXNlTmFtZXNbaV0gKyBcIi5tZFwiO1xuXG5cdFx0XHRcdFx0XHRhd2FpdCB0aGlzLnNhdmVUb0ZpbGUobmV3QmFzZW5hbWUsIGNvbnRlbnQpO1xuXG5cdFx0XHRcdFx0XHRpZih0aGlzLnNldHRpbmdzLm9wZW5FeHBsb2RlZE5vdGVzKSB7XG5cdFx0XHRcdFx0XHRcdGF3YWl0IHRoaXMuYXBwLndvcmtzcGFjZS5vcGVuTGlua1RleHQobmV3QmFzZW5hbWUsIG5ld0Jhc2VuYW1lLCB0cnVlKTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblxuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0bmV3IE5vdGljZShcIk5vIGhpZ2hsaWdodHMgdG8gZXh0cmFjdC5cIik7XG5cdFx0XHR9XG5cdFx0fSBjYXRjaCAoZSkge1xuXHRcdFx0Y29uc29sZS5sb2coZS5tZXNzYWdlKVxuXHRcdH1cblx0fVxuXG5cdGFzeW5jIHNhdmVUb0ZpbGUoZmlsZVBhdGg6IHN0cmluZywgbWRTdHJpbmc6IHN0cmluZykge1xuXHRcdC8vSWYgZmlsZXMgZXhpc3RzIHRoZW4gYXBwZW5kIGNvbnRlbnQgdG8gZXhpc3RpbmcgZmlsZVxuXHRcdGNvbnN0IGZpbGVFeGlzdHMgPSBhd2FpdCB0aGlzLmFwcC52YXVsdC5hZGFwdGVyLmV4aXN0cyhmaWxlUGF0aCk7XG5cdFx0aWYgKGZpbGVFeGlzdHMpIHtcblx0XHRcdC8vIGNvbnNvbGUubG9nKFwiRmlsZSBleGlzdHMgYWxyZWFkeS4uLlwiKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0YXdhaXQgdGhpcy5hcHAudmF1bHQuY3JlYXRlKGZpbGVQYXRoLCBtZFN0cmluZyk7XG5cdFx0fVxuXHR9XG5cblx0cHJvY2Vzc0hpZ2hsaWdodHModmlldzogYW55KSB7XG5cblx0XHR2YXIgcmU7XG5cblx0XHRpZih0aGlzLnNldHRpbmdzLnVzZUJvbGRGb3JIaWdobGlnaHRzKSB7XG5cdFx0XHRyZSA9IC8oPT18XFw8bWFya1xcPnxcXCpcXCopKFtcXHNcXFNdKj8pKD09fFxcPFxcL21hcmtcXD58XFwqXFwqKS9nO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRyZSA9IC8oPT18XFw8bWFya1xcPikoW1xcc1xcU10qPykoPT18XFw8XFwvbWFya1xcPikvZztcblx0XHR9XG5cblx0XHRsZXQgbWFya2Rvd25UZXh0ID0gdmlldy5kYXRhO1xuXHRcdGxldCBiYXNlbmFtZSA9IHZpZXcuZmlsZS5iYXNlbmFtZTtcblx0XHRsZXQgbWF0Y2hlcyA9IG1hcmtkb3duVGV4dC5tYXRjaChyZSk7XG5cdFx0dGhpcy5jb3VudGVyICs9IDE7XG5cblx0XHR2YXIgcmVzdWx0ID0gXCJcIjtcblx0XHR2YXIgaGlnaGxpZ2h0cyA9IFtdO1xuXHRcdHZhciBiYXNlTmFtZXMgPSBbXTtcblx0XHRsZXQgY29udGV4dHM6IGFueVtdW10gPSBbXTtcblx0XHRsZXQgbGluZXMgPSBtYXJrZG93blRleHQuc3BsaXQoXCJcXG5cIik7XG5cdFx0bGV0IGNsZWFuZWRMaW5lcyA9IFtdO1xuXG5cdFx0Zm9yKHZhciBpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7XG5cdFx0XHRpZighKGxpbmVzW2ldID09IFwiXCIpKSB7XG5cdFx0XHRcdGNsZWFuZWRMaW5lcy5wdXNoKGxpbmVzW2ldKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRpZiAobWF0Y2hlcyAhPSBudWxsKSB7XG5cdFx0XHRpZih0aGlzLnNldHRpbmdzLmhlYWRsaW5lVGV4dCAhPSBcIlwiKSB7IFxuXHRcdFx0XHRsZXQgdGV4dCA9IHRoaXMuc2V0dGluZ3MuaGVhZGxpbmVUZXh0LnJlcGxhY2UoL1xcJE5PVEVfVElUTEUvLCBgJHtiYXNlbmFtZX1gKVxuXHRcdFx0XHRyZXN1bHQgKz0gYCMjICR7dGV4dH1cXG5gO1xuXHRcdFx0fVxuXG5cdFx0XHRmb3IgKGxldCBlbnRyeSBvZiBtYXRjaGVzKSB7XG5cdFx0XHRcdC8vIEtlZXAgc3Vycm91bmRpbmcgcGFyYWdyYXBoIGZvciBjb250ZXh0XG5cdFx0XHRcdGlmKHRoaXMuc2V0dGluZ3MuY3JlYXRlQ29udGV4dHVhbFF1b3Rlcykge1xuXHRcdFx0XHRcdGZvcih2YXIgaSA9IDA7IGkgPCBjbGVhbmVkTGluZXMubGVuZ3RoOyBpKyspIHtcblx0XHRcdFx0XHRcdGxldCBtYXRjaCA9IGNsZWFuZWRMaW5lc1tpXS5tYXRjaChlbnRyeSk7XG5cdFx0XHRcdFx0XHRpZighKG1hdGNoID09IG51bGwpICYmIG1hdGNoLmxlbmd0aCA+IDApIHtcblx0XHRcdFx0XHRcdFx0bGV0IHZhbCA9IGNsZWFuZWRMaW5lc1tpXTtcblxuXHRcdFx0XHRcdFx0XHRpZighY29udGV4dHMuY29udGFpbnModmFsKSkge1xuXHRcdFx0XHRcdFx0XHRcdGNvbnRleHRzLnB1c2godmFsKTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXG5cdFx0XHRcdC8vIENsZWFuIHVwIGhpZ2hsaWdodGluZyBtYXRjaFxuXHRcdFx0XHR2YXIgcmVtb3ZlTmV3bGluZSA9IGVudHJ5LnJlcGxhY2UoL1xcbi9nLCBcIiBcIik7XG5cdFx0XHRcdGxldCByZW1vdmVIaWdobGlnaHRTdGFydCA9IHJlbW92ZU5ld2xpbmUucmVwbGFjZSgvPT0vZywgXCJcIilcblx0XHRcdFx0bGV0IHJlbW92ZUhpZ2hsaWdodEVuZCA9IHJlbW92ZUhpZ2hsaWdodFN0YXJ0LnJlcGxhY2UoL1xcPG1hcmtcXD4vZywgXCJcIilcblx0XHRcdFx0bGV0IHJlbW92ZU1hcmtDbG9zaW5nID0gcmVtb3ZlSGlnaGxpZ2h0RW5kLnJlcGxhY2UoL1xcPFxcL21hcmtcXD4vZywgXCJcIilcblx0XHRcdFx0bGV0IHJlbW92ZUJvbGQgPSByZW1vdmVNYXJrQ2xvc2luZy5yZXBsYWNlKC9cXCpcXCovZywgXCJcIilcblx0XHRcdFx0bGV0IHJlbW92ZURvdWJsZVNwYWNlcyA9IHJlbW92ZUJvbGQucmVwbGFjZShcIiAgXCIsIFwiIFwiKTtcblxuXHRcdFx0XHRyZW1vdmVEb3VibGVTcGFjZXMgPSByZW1vdmVEb3VibGVTcGFjZXMucmVwbGFjZShcIiAgXCIsIFwiIFwiKTtcblx0XHRcdFx0cmVtb3ZlRG91YmxlU3BhY2VzID0gcmVtb3ZlRG91YmxlU3BhY2VzLnRyaW0oKTtcblxuXHRcdFx0XHRpZih0aGlzLnNldHRpbmdzLmF1dG9DYXBpdGFsaXplKSB7XG5cdFx0XHRcdFx0aWYocmVtb3ZlRG91YmxlU3BhY2VzICE9IG51bGwpIHtcblx0XHRcdFx0XHRcdHJlbW92ZURvdWJsZVNwYWNlcyA9IHRoaXMuY2FwaXRhbGl6ZUZpcnN0TGV0dGVyKHJlbW92ZURvdWJsZVNwYWNlcyk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cblx0XHRcdFx0cmVzdWx0ICs9IFwiLSBcIlxuXG5cdFx0XHRcdGlmKHRoaXMuc2V0dGluZ3MuY3JlYXRlTGlua3MpIHtcblx0XHRcdFx0XHQvLyBGaXJzdCwgc2FuaXRpemUgaGlnaGxpZ2h0IHRvIGJlIHVzZWQgYXMgYSBmaWxlLWxpbmtcblx0XHRcdFx0XHQvLyAqIFwiIFxcIC8gfCA8ID4gOiA/XG5cdFx0XHRcdFx0bGV0IHNhbml0aXplZCA9IHJlbW92ZURvdWJsZVNwYWNlcy5yZXBsYWNlKC9cXCp8XFxcInxcXFxcfFxcL3xcXDx8XFw+fFxcOnxcXD98XFx8L2dtLCBcIlwiKTtcblx0XHRcdFx0XHRzYW5pdGl6ZWQgPSBzYW5pdGl6ZWQudHJpbSgpO1xuXG5cdFx0XHRcdFx0bGV0IGJhc2VOYW1lID0gc2FuaXRpemVkO1xuXHRcdFx0XHRcdGlmKGJhc2VOYW1lLmxlbmd0aCA+IDEwMCkge1xuXHRcdFx0XHRcdFx0YmFzZU5hbWUgPSBiYXNlTmFtZS5zdWJzdHIoMCwgOTkpO1xuXHRcdFx0XHRcdFx0YmFzZU5hbWUgKz0gXCIuLi5cIlxuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHJlc3VsdCArPSBcIltbXCIgKyBiYXNlTmFtZSArIFwiXV1cIjtcblx0XHRcdFx0XHRoaWdobGlnaHRzLnB1c2goc2FuaXRpemVkKTtcblx0XHRcdFx0XHRiYXNlTmFtZXMucHVzaChiYXNlTmFtZSk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0cmVzdWx0ICs9IHJlbW92ZURvdWJsZVNwYWNlcztcblx0XHRcdFx0XHRoaWdobGlnaHRzLnB1c2gocmVtb3ZlRG91YmxlU3BhY2VzKTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdGlmKHRoaXMuc2V0dGluZ3MuYWRkRm9vdG5vdGVzKSB7XG5cdFx0XHRcdFx0cmVzdWx0ICs9IGBbXiR7dGhpcy5jb3VudGVyfV1gO1xuXHRcdFx0XHR9IFxuXG5cdFx0XHRcdHJlc3VsdCArPSBcIlxcblwiO1xuXHRcdFx0fVxuXG5cdFx0XHRpZih0aGlzLnNldHRpbmdzLmFkZEZvb3Rub3Rlcykge1xuXHRcdFx0XHRyZXN1bHQgKz0gXCJcXG5cIlxuXHRcdFx0XHRyZXN1bHQgKz0gYFteJHt0aGlzLmNvdW50ZXJ9XTogW1ske2Jhc2VuYW1lfV1dXFxuYFxuXHRcdFx0fVxuXG5cdFx0XHRyZXN1bHQgKz0gXCJcXG5cIjtcblx0XHR9XG5cblx0XHRyZXR1cm4ge21hcmtkb3duOiByZXN1bHQsIGJhc2VOYW1lczogYmFzZU5hbWVzLCBoaWdobGlnaHRzOiBoaWdobGlnaHRzLCBjb250ZXh0czogY29udGV4dHN9O1xuXHR9XG5cblx0c2F2ZVRvQ2xpcGJvYXJkKGRhdGE6IHN0cmluZyk6IHN0cmluZyB7XG5cdFx0aWYgKGRhdGEubGVuZ3RoID4gMCkge1xuXHRcdFx0bmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQoZGF0YSk7XG5cdFx0XHRyZXR1cm4gXCJIaWdobGlnaHRzIGNvcGllZCB0byBjbGlwYm9hcmQhXCI7XG5cdFx0fSBlbHNlIHtcblx0XHRcdHJldHVybiBcIk5vIGhpZ2hsaWdodHMgZm91bmRcIjtcblx0XHR9XG5cdH1cblxuXHRjcmVhdGVIaWdobGlnaHQoKSB7XG5cdFx0Y29uc3QgbWRWaWV3ID0gdGhpcy5hcHAud29ya3NwYWNlLmFjdGl2ZUxlYWYudmlldyBhcyBNYXJrZG93blZpZXc7XG5cdFx0Y29uc3QgZG9jID0gbWRWaWV3LnNvdXJjZU1vZGUuY21FZGl0b3I7XG5cdFx0dGhpcy5lZGl0b3IgPSBkb2M7XG5cblx0XHRjb25zdCBjdXJzb3JQb3NpdGlvbiA9IHRoaXMuZWRpdG9yLmdldEN1cnNvcigpO1xuXHRcdGxldCBsaW5lVGV4dCA9IHRoaXMuZWRpdG9yLmdldExpbmUoY3Vyc29yUG9zaXRpb24ubGluZSk7XG5cblx0XHQvLyB1c2Ugb3VyIGZhbmN5IGNsYXNzIHRvIGZpZ3VyZSB0aGlzIG91dFxuXHRcdGxldCB0aCA9IG5ldyBUb2dnbGVIaWdobGlnaHQoKTtcblx0XHRsZXQgcmVzdWx0ID0gdGgudG9nZ2xlSGlnaGxpZ2h0KGxpbmVUZXh0LCBjdXJzb3JQb3NpdGlvbi5jaCk7XG5cblx0XHQvLyBjYXRjaCB1cCBvbiBjdXJzb3Jcblx0XHRsZXQgY3Vyc29yRGlmZmVyZW5jZSA9IC0yO1xuXHRcdGlmKHJlc3VsdC5sZW5ndGggPiBsaW5lVGV4dC5sZW5ndGgpIHsgY3Vyc29yRGlmZmVyZW5jZSA9IDIgfVxuXG5cdFx0dGhpcy5lZGl0b3IucmVwbGFjZVJhbmdlKHJlc3VsdCwge2xpbmU6IGN1cnNvclBvc2l0aW9uLmxpbmUsIGNoOiAwfSwge2xpbmU6IGN1cnNvclBvc2l0aW9uLmxpbmUsIGNoOiBsaW5lVGV4dC5sZW5ndGh9KVxuXHRcdHRoaXMuZWRpdG9yLnNldEN1cnNvcih7bGluZTogY3Vyc29yUG9zaXRpb24ubGluZSwgY2g6IGN1cnNvclBvc2l0aW9uLmNoICsgY3Vyc29yRGlmZmVyZW5jZX0pO1xuXHR9XG5cblxuXHRjYXBpdGFsaXplRmlyc3RMZXR0ZXIoczogc3RyaW5nKSB7XG5cdFx0cmV0dXJuIHMuY2hhckF0KDApLnRvVXBwZXJDYXNlKCkgKyBzLnNsaWNlKDEpO1xuXHR9XG59XG4iXSwibmFtZXMiOlsiU2V0dGluZyIsIlBsdWdpblNldHRpbmdUYWIiLCJhZGRJY29uIiwiTm90aWNlIiwiUGx1Z2luIl0sIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJLGFBQWEsR0FBRyxTQUFTLENBQUMsRUFBRSxDQUFDLEVBQUU7QUFDbkMsSUFBSSxhQUFhLEdBQUcsTUFBTSxDQUFDLGNBQWM7QUFDekMsU0FBUyxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUUsWUFBWSxLQUFLLElBQUksVUFBVSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQ3BGLFFBQVEsVUFBVSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQ25GLElBQUksT0FBTyxhQUFhLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQy9CLENBQUMsQ0FBQztBQUNGO0FBQ08sU0FBUyxTQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRTtBQUNoQyxJQUFJLGFBQWEsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDeEIsSUFBSSxTQUFTLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxXQUFXLEdBQUcsQ0FBQyxDQUFDLEVBQUU7QUFDM0MsSUFBSSxDQUFDLENBQUMsU0FBUyxHQUFHLENBQUMsS0FBSyxJQUFJLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3pGLENBQUM7QUF1Q0Q7QUFDTyxTQUFTLFNBQVMsQ0FBQyxPQUFPLEVBQUUsVUFBVSxFQUFFLENBQUMsRUFBRSxTQUFTLEVBQUU7QUFDN0QsSUFBSSxTQUFTLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxPQUFPLEtBQUssWUFBWSxDQUFDLEdBQUcsS0FBSyxHQUFHLElBQUksQ0FBQyxDQUFDLFVBQVUsT0FBTyxFQUFFLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUU7QUFDaEgsSUFBSSxPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxPQUFPLENBQUMsRUFBRSxVQUFVLE9BQU8sRUFBRSxNQUFNLEVBQUU7QUFDL0QsUUFBUSxTQUFTLFNBQVMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFO0FBQ25HLFFBQVEsU0FBUyxRQUFRLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFO0FBQ3RHLFFBQVEsU0FBUyxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsTUFBTSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQyxFQUFFO0FBQ3RILFFBQVEsSUFBSSxDQUFDLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLFVBQVUsSUFBSSxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQzlFLEtBQUssQ0FBQyxDQUFDO0FBQ1AsQ0FBQztBQUNEO0FBQ08sU0FBUyxXQUFXLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRTtBQUMzQyxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDckgsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsT0FBTyxNQUFNLEtBQUssVUFBVSxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEdBQUcsV0FBVyxFQUFFLE9BQU8sSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUM3SixJQUFJLFNBQVMsSUFBSSxDQUFDLENBQUMsRUFBRSxFQUFFLE9BQU8sVUFBVSxDQUFDLEVBQUUsRUFBRSxPQUFPLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFO0FBQ3RFLElBQUksU0FBUyxJQUFJLENBQUMsRUFBRSxFQUFFO0FBQ3RCLFFBQVEsSUFBSSxDQUFDLEVBQUUsTUFBTSxJQUFJLFNBQVMsQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFDO0FBQ3RFLFFBQVEsT0FBTyxDQUFDLEVBQUUsSUFBSTtBQUN0QixZQUFZLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ3pLLFlBQVksSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNwRCxZQUFZLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUN6QixnQkFBZ0IsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTTtBQUM5QyxnQkFBZ0IsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO0FBQ3hFLGdCQUFnQixLQUFLLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTO0FBQ2pFLGdCQUFnQixLQUFLLENBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxTQUFTO0FBQ2pFLGdCQUFnQjtBQUNoQixvQkFBb0IsSUFBSSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVMsRUFBRTtBQUNoSSxvQkFBb0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRTtBQUMxRyxvQkFBb0IsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxD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 534 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "extract-highlights-plugin", 3 | "name": "Extract Highlights", 4 | "version": "0.0.18", 5 | "description": "Create, extract and leverage your markdown highlights", 6 | "author": "Alexis Rondeau", 7 | "authorUrl": "https://publish.obsidian.md/alexisrondeau", 8 | "js": "main.js" 9 | } 10 | -------------------------------------------------------------------------------- /onoff.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/onoff.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extract-highlights-plugin", 3 | "version": "0.0.18", 4 | "description": "his is a shortcut-based plugin extracts all ==highlights== in a note into your clipboard", 5 | "main": "ExtractHighlightsPlugin.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "rollup --config rollup.config.js -w", 9 | "build": "rollup --config rollup.config.js", 10 | "test": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/**/*.ts", 11 | "test:watch": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' mocha -r ts-node/register -r ignore-styles -r jsdom-global/register --watch --watch-files src, test/**/*.ts" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@rollup/plugin-commonjs": "^15.1.0", 18 | "@rollup/plugin-node-resolve": "^9.0.0", 19 | "@rollup/plugin-typescript": "^6.0.0", 20 | "@types/chai": "^4.2.14", 21 | "@types/mocha": "^8.2.0", 22 | "@types/node": "^14.14.14", 23 | "chai": "^4.2.0", 24 | "cross-env": "^7.0.2", 25 | "ignore-styles": "^5.0.1", 26 | "jsdom": "^16.4.0", 27 | "jsdom-global": "^3.0.2", 28 | "mocha": "^8.2.1", 29 | "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master", 30 | "rollup": "^2.35.1", 31 | "ts-node": "^9.1.1", 32 | "tslib": "^1.14.1", 33 | "typescript": "^4.1.3" 34 | }, 35 | "dependencies": { 36 | "electron": "^11.2.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | 5 | export default { 6 | input: 'src/main.ts', 7 | output: { 8 | dir: '.', 9 | sourcemap: 'inline', 10 | format: 'cjs', 11 | exports: 'default' 12 | }, 13 | external: ['obsidian'], 14 | plugins: [ 15 | typescript(), 16 | nodeResolve({browser: true}), 17 | commonjs(), 18 | ] 19 | }; -------------------------------------------------------------------------------- /secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/secret.png -------------------------------------------------------------------------------- /secrettips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/secrettips.png -------------------------------------------------------------------------------- /src/ExtractHighlightsPluginSettings.ts: -------------------------------------------------------------------------------- 1 | export default class ExtractHighlightsPluginSettings { 2 | public headlineText: string; 3 | public addFootnotes: boolean; 4 | public useBoldForHighlights: boolean; 5 | public createLinks: boolean; 6 | public autoCapitalize: boolean; 7 | public createNewFile: boolean; 8 | public explodeIntoNotes: boolean; 9 | public openExplodedNotes: boolean; 10 | public createContextualQuotes: boolean; 11 | 12 | constructor() { 13 | this.headlineText = ""; 14 | this.addFootnotes = false; 15 | this.useBoldForHighlights = false; 16 | this.createLinks = false; 17 | this.autoCapitalize = false; 18 | this.createNewFile = false; 19 | this.explodeIntoNotes = false; 20 | this.openExplodedNotes = false; 21 | this.createContextualQuotes = false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ExtractHighlightsPluginSettingsTab.ts: -------------------------------------------------------------------------------- 1 | import {App, PluginSettingTab, Setting} from "obsidian"; 2 | import ExtractHighlightsPlugin from "./main"; 3 | 4 | export default class ExtractHighlightsPluginSettingsTab extends PluginSettingTab { 5 | private readonly plugin: ExtractHighlightsPlugin; 6 | 7 | constructor(app: App, plugin: ExtractHighlightsPlugin) { 8 | super(app, plugin); 9 | this.plugin = plugin; 10 | } 11 | 12 | display(): void { 13 | const {containerEl} = this; 14 | 15 | containerEl.empty(); 16 | 17 | containerEl.createEl("h2", {text: "Extract Highlights Plugin"}); 18 | 19 | new Setting(containerEl) 20 | .setName("Heading Text") 21 | .setDesc("If set, will add `## Your Text`. Use $NOTE_TITLE to include title. Leave blank to omit. ") 22 | .addText((text) => 23 | text 24 | .setPlaceholder("Highlights for $NOTE_TITLE") 25 | .setValue(this.plugin.settings.headlineText) 26 | .onChange((value) => { 27 | this.plugin.settings.headlineText = value; 28 | this.plugin.saveData(this.plugin.settings); 29 | }) 30 | ); 31 | 32 | new Setting(containerEl) 33 | .setName('Use bold for highlights') 34 | .setDesc( 35 | 'If enabled, will include classic markdown bold (**) sections as highlights', 36 | ) 37 | .addToggle((toggle) => 38 | toggle.setValue(this.plugin.settings.useBoldForHighlights).onChange((value) => { 39 | this.plugin.settings.useBoldForHighlights = value; 40 | this.plugin.saveData(this.plugin.settings); 41 | }), 42 | ); 43 | 44 | 45 | new Setting(containerEl) 46 | .setName('Enable Footnotes') 47 | .setDesc( 48 | 'If enabled, will add a footnote to the current document to each highlight in your list. Useful when you wan to keep track of which highlight came from which source file.', 49 | ) 50 | .addToggle((toggle) => 51 | toggle.setValue(this.plugin.settings.addFootnotes).onChange((value) => { 52 | this.plugin.settings.addFootnotes = value; 53 | this.plugin.saveData(this.plugin.settings); 54 | }), 55 | ); 56 | 57 | new Setting(containerEl) 58 | .setName('Auto-capitalize first letter') 59 | .setDesc( 60 | 'If enabled, capitalizes the first letter of each highlight.', 61 | ) 62 | .addToggle((toggle) => 63 | toggle.setValue(this.plugin.settings.autoCapitalize).onChange((value) => { 64 | this.plugin.settings.autoCapitalize = value; 65 | this.plugin.saveData(this.plugin.settings); 66 | }), 67 | ); 68 | 69 | 70 | new Setting(containerEl) 71 | .setName('Create links') 72 | .setDesc( 73 | 'If enabled, will turn each highlight into a [[ link ]] to create a highlight MOC', 74 | ) 75 | .addToggle((toggle) => 76 | toggle.setValue(this.plugin.settings.createLinks).onChange((value) => { 77 | this.plugin.settings.createLinks = value; 78 | 79 | // disable explode notes mode 80 | if(this.plugin.settings.explodeIntoNotes && value == false) { 81 | this.plugin.settings.explodeIntoNotes = false; 82 | this.plugin.settings.openExplodedNotes = false; 83 | } 84 | 85 | this.plugin.saveData(this.plugin.settings); 86 | }), 87 | ); 88 | 89 | new Setting(containerEl) 90 | .setName('Open new file with highlights') 91 | .setDesc( 92 | 'If enabled, opens a new file with the highlights copied into.', 93 | ) 94 | .addToggle((toggle) => 95 | toggle.setValue(this.plugin.settings.createNewFile).onChange((value) => { 96 | this.plugin.settings.createNewFile = value; 97 | 98 | // disable explode notes mode 99 | if(this.plugin.settings.explodeIntoNotes && value == false) { 100 | this.plugin.settings.explodeIntoNotes = false; 101 | this.plugin.settings.openExplodedNotes = false; 102 | } 103 | 104 | this.plugin.saveData(this.plugin.settings); 105 | }), 106 | ); 107 | 108 | containerEl.createEl("h2", {text: "💥 Explode Notes Mode 💥"}); 109 | containerEl.createEl("p", {text: "A secret mode that will take your highlighting to the next level. Only available if you have 'Create Links' and 'Create new File' enabled. After enabling both, close this window and open again to see options."}); 110 | 111 | if(this.plugin.settings.createLinks && this.plugin.settings.createNewFile) { 112 | new Setting(containerEl) 113 | .setName('Explode links into notes') 114 | .setDesc( 115 | 'If enabled, will turn each highlight into a note with the highlighted text as quote and a backlink to the MOC and source-file. Very powerful but use with caution!', 116 | ) 117 | .addToggle((toggle) => 118 | toggle.setValue(this.plugin.settings.explodeIntoNotes).onChange((value) => { 119 | this.plugin.settings.explodeIntoNotes = value; 120 | this.plugin.saveData(this.plugin.settings); 121 | }), 122 | ); 123 | 124 | new Setting(containerEl) 125 | .setName('Open exploded notes on creation') 126 | .setDesc( 127 | 'If enabled, will open each of your exploded notes when you create them. Fun and useful to continue working in your highlight-notes right away!', 128 | ) 129 | .addToggle((toggle) => 130 | toggle.setValue(this.plugin.settings.openExplodedNotes).onChange((value) => { 131 | this.plugin.settings.openExplodedNotes = value; 132 | this.plugin.saveData(this.plugin.settings); 133 | }), 134 | ); 135 | 136 | new Setting(containerEl) 137 | .setName('Create contextual quotes') 138 | .setDesc( 139 | 'If enabled, will quote the full line of your highlight, not just the highlight itself. Useful for keeping the context of your highlight.', 140 | ) 141 | .addToggle((toggle) => 142 | toggle.setValue(this.plugin.settings.createContextualQuotes).onChange((value) => { 143 | this.plugin.settings.createContextualQuotes = value; 144 | this.plugin.saveData(this.plugin.settings); 145 | }), 146 | ); 147 | 148 | } 149 | 150 | } 151 | } -------------------------------------------------------------------------------- /src/ProcessHighlights.ts: -------------------------------------------------------------------------------- 1 | export default class ProcessHighlights { 2 | private includeBold: boolean; 3 | 4 | constructor(includeBold: boolean) { 5 | this.includeBold = includeBold; 6 | } 7 | 8 | process(data: string) { 9 | 10 | let re: RegExp; 11 | 12 | if(this.includeBold) { 13 | console.log("include Bold"); 14 | re = /(==|\|\*\*)([\s\S]*?)(==|\<\/mark\>|\*\*)/g; 15 | } else { 16 | console.log("Don't include Bold"); 17 | re = /(==|\)([\s\S]*?)(==|\<\/mark\>)/g; 18 | } 19 | 20 | let matches = data.match(re); 21 | 22 | var result = ""; 23 | 24 | if(matches !== null) { 25 | for (let entry of matches) { 26 | var clean = ""; 27 | clean = entry.replace(/==/g, ""); 28 | clean = clean.replace(/\/g, ""); 29 | clean = clean.replace(/\<\/mark\>/g, ""); 30 | clean = clean.replace(/\*\*/g, ""); 31 | 32 | clean = "- " + clean; 33 | clean = clean + "\n"; 34 | result += clean; 35 | } 36 | } 37 | 38 | return result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ToggleHighlight.ts: -------------------------------------------------------------------------------- 1 | export default class ToggleHighlight { 2 | 3 | toggleHighlight(s: string, ch?: number) { 4 | if(s == "") return ""; 5 | if(s.indexOf(".") < 0) { return "==" + s + "=="} 6 | 7 | let left = s.substring(0, ch); 8 | let right = s.substring(ch); 9 | let marked = left + "$CURSOR$" + right; 10 | 11 | // https://regex101.com/r/BSpvV6/7 12 | // https://stackoverflow.com/a/5553924 13 | let p = marked.match(/(==(.*?)==)|[^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$)/gm); 14 | 15 | let np = new Array(); 16 | 17 | if(p.length > 0) { 18 | p.forEach(function (part) { 19 | if(typeof part !== 'undefined' ) { 20 | if(part.trim() == "") { return; } 21 | 22 | if(part.includes("$CURSOR$")) { 23 | 24 | if(part.startsWith("==") && part.endsWith("==")) { 25 | part = part.replace(/==/g, ""); 26 | } else { 27 | part = "==" + part + "=="; 28 | } 29 | part = part.replace("$CURSOR$", ""); 30 | part = part.trim(); 31 | } 32 | part = part.trim(); 33 | np.push(part); 34 | } 35 | }); 36 | 37 | return np.join(" "); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {Plugin, Notice, addIcon, View, MarkdownView, Workspace} from "obsidian" 2 | import ExtractHighlightsPluginSettings from "./ExtractHighlightsPluginSettings" 3 | import ExtractHighlightsPluginSettingsTab from "./ExtractHighlightsPluginSettingsTab" 4 | import ToggleHighlight from "./ToggleHighlight"; 5 | 6 | addIcon('target', '') 7 | 8 | export default class ExtractHighlightsPlugin extends Plugin { 9 | 10 | public settings: ExtractHighlightsPluginSettings; 11 | public statusBar: HTMLElement 12 | public counter: 0; 13 | private editor: CodeMirror.Editor; 14 | 15 | async onload() { 16 | this.counter = 0; 17 | this.loadSettings(); 18 | this.addSettingTab(new ExtractHighlightsPluginSettingsTab(this.app, this)); 19 | 20 | this.statusBar = this.addStatusBarItem() 21 | 22 | this.addRibbonIcon('target', 'Extract Highlights', () => { 23 | this.extractHighlights(); 24 | }); 25 | 26 | this.addCommand({ 27 | id: "shortcut-extract-highlights", 28 | name: "Shortcut for extracting highlights", 29 | callback: () => this.extractHighlights(), 30 | hotkeys: [ 31 | { 32 | modifiers: ["Alt", "Shift"], 33 | key: "±", 34 | }, 35 | ], 36 | }); 37 | 38 | this.addCommand({ 39 | id: "shortcut-highlight-sentence", 40 | name: "Shortcut for highlighting sentence cursor is in", 41 | callback: () => this.createHighlight(), 42 | hotkeys: [ 43 | { 44 | modifiers: ["Alt", "Shift"], 45 | key: "—", 46 | }, 47 | ], 48 | }); 49 | } 50 | 51 | loadSettings() { 52 | this.settings = new ExtractHighlightsPluginSettings(); 53 | (async () => { 54 | const loadedSettings = await this.loadData(); 55 | if (loadedSettings) { 56 | // console.log("Found existing settings file"); 57 | this.settings.headlineText = loadedSettings.headlineText; 58 | this.settings.addFootnotes = loadedSettings.addFootnotes; 59 | this.settings.createLinks = loadedSettings.createLinks; 60 | this.settings.autoCapitalize = loadedSettings.autoCapitalize; 61 | this.settings.createNewFile = loadedSettings.createNewFile; 62 | this.settings.explodeIntoNotes = loadedSettings.explodeIntoNotes; 63 | this.settings.openExplodedNotes = loadedSettings.openExplodedNotes; 64 | this.settings.createContextualQuotes = loadedSettings.createContextualQuotes; 65 | } else { 66 | // console.log("No settings file found, saving..."); 67 | this.saveData(this.settings); 68 | } 69 | })(); 70 | } 71 | 72 | async extractHighlights() { 73 | let activeLeaf: any = this.app.workspace.activeLeaf ?? null 74 | 75 | let name = activeLeaf?.view.file.basename; 76 | 77 | try { 78 | if (activeLeaf?.view?.data) { 79 | let processResults = this.processHighlights(activeLeaf.view); 80 | let highlightsText = processResults.markdown; 81 | let highlights = processResults.highlights; 82 | let baseNames = processResults.baseNames; 83 | let contexts = processResults.contexts; 84 | let saveStatus = this.saveToClipboard(highlightsText); 85 | new Notice(saveStatus); 86 | 87 | const newBasenameMOC = "Highlights for " + name + ".md"; 88 | if (this.settings.createNewFile) { 89 | // Add link back to Original 90 | highlightsText += `## Source\n- [[${name}]]`; 91 | 92 | await this.saveToFile(newBasenameMOC, highlightsText); 93 | await this.app.workspace.openLinkText(newBasenameMOC, newBasenameMOC, true); 94 | } 95 | 96 | if(this.settings.createNewFile && this.settings.createLinks && this.settings.explodeIntoNotes) { 97 | for(var i = 0; i < baseNames.length; i++) { 98 | // console.log("Creating file for " + baseNames[i]); 99 | var content = ""; 100 | // add highlight as quote 101 | content += "## Source\n" 102 | if(this.settings.createContextualQuotes) { 103 | // context quote 104 | content += `> ${contexts[i]}[^1]`; 105 | } else { 106 | // regular highlight quote 107 | content += `> ${highlights[i]}[^1]`; 108 | } 109 | content += "\n\n"; 110 | content += `[^1]: [[${name}]]`; 111 | content += "\n"; 112 | // console.log(content); 113 | 114 | const newBasename = baseNames[i] + ".md"; 115 | 116 | await this.saveToFile(newBasename, content); 117 | 118 | if(this.settings.openExplodedNotes) { 119 | await this.app.workspace.openLinkText(newBasename, newBasename, true); 120 | } 121 | } 122 | } 123 | 124 | } else { 125 | new Notice("No highlights to extract."); 126 | } 127 | } catch (e) { 128 | console.log(e.message) 129 | } 130 | } 131 | 132 | async saveToFile(filePath: string, mdString: string) { 133 | //If files exists then append content to existing file 134 | const fileExists = await this.app.vault.adapter.exists(filePath); 135 | if (fileExists) { 136 | // console.log("File exists already..."); 137 | } else { 138 | await this.app.vault.create(filePath, mdString); 139 | } 140 | } 141 | 142 | processHighlights(view: any) { 143 | 144 | var re; 145 | 146 | if(this.settings.useBoldForHighlights) { 147 | re = /(==|\|\*\*)([\s\S]*?)(==|\<\/mark\>|\*\*)/g; 148 | } else { 149 | re = /(==|\)([\s\S]*?)(==|\<\/mark\>)/g; 150 | } 151 | 152 | let markdownText = view.data; 153 | let basename = view.file.basename; 154 | let matches = markdownText.match(re); 155 | this.counter += 1; 156 | 157 | var result = ""; 158 | var highlights = []; 159 | var baseNames = []; 160 | let contexts: any[][] = []; 161 | let lines = markdownText.split("\n"); 162 | let cleanedLines = []; 163 | 164 | for(var i = 0; i < lines.length; i++) { 165 | if(!(lines[i] == "")) { 166 | cleanedLines.push(lines[i]); 167 | } 168 | } 169 | 170 | if (matches != null) { 171 | if(this.settings.headlineText != "") { 172 | let text = this.settings.headlineText.replace(/\$NOTE_TITLE/, `${basename}`) 173 | result += `## ${text}\n`; 174 | } 175 | 176 | for (let entry of matches) { 177 | // Keep surrounding paragraph for context 178 | if(this.settings.createContextualQuotes) { 179 | for(var i = 0; i < cleanedLines.length; i++) { 180 | let match = cleanedLines[i].match(entry); 181 | if(!(match == null) && match.length > 0) { 182 | let val = cleanedLines[i]; 183 | 184 | if(!contexts.contains(val)) { 185 | contexts.push(val); 186 | } 187 | } 188 | } 189 | } 190 | 191 | // Clean up highlighting match 192 | var removeNewline = entry.replace(/\n/g, " "); 193 | let removeHighlightStart = removeNewline.replace(/==/g, "") 194 | let removeHighlightEnd = removeHighlightStart.replace(/\/g, "") 195 | let removeMarkClosing = removeHighlightEnd.replace(/\<\/mark\>/g, "") 196 | let removeBold = removeMarkClosing.replace(/\*\*/g, "") 197 | let removeDoubleSpaces = removeBold.replace(" ", " "); 198 | 199 | removeDoubleSpaces = removeDoubleSpaces.replace(" ", " "); 200 | removeDoubleSpaces = removeDoubleSpaces.trim(); 201 | 202 | if(this.settings.autoCapitalize) { 203 | if(removeDoubleSpaces != null) { 204 | removeDoubleSpaces = this.capitalizeFirstLetter(removeDoubleSpaces); 205 | } 206 | } 207 | 208 | result += "- " 209 | 210 | if(this.settings.createLinks) { 211 | // First, sanitize highlight to be used as a file-link 212 | // * " \ / | < > : ? 213 | let sanitized = removeDoubleSpaces.replace(/\*|\"|\\|\/|\<|\>|\:|\?|\|/gm, ""); 214 | sanitized = sanitized.trim(); 215 | 216 | let baseName = sanitized; 217 | if(baseName.length > 100) { 218 | baseName = baseName.substr(0, 99); 219 | baseName += "..." 220 | } 221 | 222 | result += "[[" + baseName + "]]"; 223 | highlights.push(sanitized); 224 | baseNames.push(baseName); 225 | } else { 226 | result += removeDoubleSpaces; 227 | highlights.push(removeDoubleSpaces); 228 | } 229 | 230 | if(this.settings.addFootnotes) { 231 | result += `[^${this.counter}]`; 232 | } 233 | 234 | result += "\n"; 235 | } 236 | 237 | if(this.settings.addFootnotes) { 238 | result += "\n" 239 | result += `[^${this.counter}]: [[${basename}]]\n` 240 | } 241 | 242 | result += "\n"; 243 | } 244 | 245 | return {markdown: result, baseNames: baseNames, highlights: highlights, contexts: contexts}; 246 | } 247 | 248 | saveToClipboard(data: string): string { 249 | if (data.length > 0) { 250 | navigator.clipboard.writeText(data); 251 | return "Highlights copied to clipboard!"; 252 | } else { 253 | return "No highlights found"; 254 | } 255 | } 256 | 257 | createHighlight() { 258 | const mdView = this.app.workspace.activeLeaf.view as MarkdownView; 259 | const doc = mdView.sourceMode.cmEditor; 260 | this.editor = doc; 261 | 262 | const cursorPosition = this.editor.getCursor(); 263 | let lineText = this.editor.getLine(cursorPosition.line); 264 | 265 | // use our fancy class to figure this out 266 | let th = new ToggleHighlight(); 267 | let result = th.toggleHighlight(lineText, cursorPosition.ch); 268 | 269 | // catch up on cursor 270 | let cursorDifference = -2; 271 | if(result.length > lineText.length) { cursorDifference = 2 } 272 | 273 | this.editor.replaceRange(result, {line: cursorPosition.line, ch: 0}, {line: cursorPosition.line, ch: lineText.length}) 274 | this.editor.setCursor({line: cursorPosition.line, ch: cursorPosition.ch + cursorDifference}); 275 | } 276 | 277 | 278 | capitalizeFirstLetter(s: string) { 279 | return s.charAt(0).toUpperCase() + s.slice(1); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/styles.css -------------------------------------------------------------------------------- /test/ProcessHighlightsTest.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import {assert} from 'chai'; 3 | import ToggleHighlight from "../src/ProcessHighlights"; 4 | 5 | let subject: ToggleHighlight = null; 6 | 7 | describe("Process Highlights", () => { 8 | before(async () => { 9 | subject = new ToggleHighlight(false); 10 | }); 11 | 12 | describe("Empty input processing", () => { 13 | it("Returns an empty string", () => { 14 | const result = subject.process(""); 15 | assert.equal(result, ""); 16 | }); 17 | 18 | it("Returns an empty string if there's no highlight", () => { 19 | const result = subject.process("Foo."); 20 | assert.equal(result, ""); 21 | }); 22 | }); 23 | 24 | describe("Extracts highlights", () => { 25 | context("Basic markdown == highlights", () => { 26 | it("Returns a single list-item", () => { 27 | const result = subject.process("==Foo.=="); 28 | assert.equal(result, "- Foo.\n"); 29 | }); 30 | 31 | it("Returns two list-items", () => { 32 | const result = subject.process("==Foo.== ==Bar.=="); 33 | assert.equal(result, "- Foo.\n- Bar.\n"); 34 | }); 35 | 36 | it("Returns only two list-items", () => { 37 | const result = subject.process("==Foo.== Baz. ==Bar.=="); 38 | assert.equal(result, "- Foo.\n- Bar.\n"); 39 | }); 40 | 41 | it("Returns two list-items from different lines", () => { 42 | const result = subject.process("==Foo.== Bar\nBaz ==Quux.== Quz\n"); 43 | assert.equal(result, "- Foo.\n- Quux.\n"); 44 | }); 45 | }); 46 | 47 | context("Default mark-tags highlights", () => { 48 | it("Returns a single list-item", () => { 49 | const result = subject.process("Foo."); 50 | assert.equal(result, "- Foo.\n"); 51 | }); 52 | 53 | it("Returns two list-items", () => { 54 | const result = subject.process("Foo. Bar.>"); 55 | assert.equal(result, "- Foo.\n- Bar.\n"); 56 | }); 57 | 58 | it("Returns only two list-items", () => { 59 | const result = subject.process("Foo. Baz. Bar."); 60 | assert.equal(result, "- Foo.\n- Bar.\n"); 61 | }); 62 | 63 | it("Returns two list-items from different lines", () => { 64 | const result = subject.process("Foo. Bar\nBaz Quux. Quz\n"); 65 | assert.equal(result, "- Foo.\n- Quux.\n"); 66 | }); 67 | }); 68 | 69 | context("Optional markdown bold highlights", () => { 70 | context("Bold highlights disabled", () => { 71 | let newSubject = new ToggleHighlight(false); 72 | 73 | it("Returns an empty string", () => { 74 | const result = newSubject.process("**Foo.**"); 75 | assert.equal(result, ""); 76 | }); 77 | }); 78 | 79 | context("Bold highlights enabled", () => { 80 | let newSubject = new ToggleHighlight(true); 81 | 82 | it("Returns a single list-item", () => { 83 | const result = newSubject.process("**Foo.**"); 84 | assert.equal(result, "- Foo.\n"); 85 | }); 86 | 87 | it("Returns two list-items", () => { 88 | const result = newSubject.process("**Foo.** **Bar.**"); 89 | assert.equal(result, "- Foo.\n- Bar.\n"); 90 | }); 91 | 92 | it("Returns only two list-items", () => { 93 | const result = newSubject.process("**Foo.** Baz. **Bar.**"); 94 | assert.equal(result, "- Foo.\n- Bar.\n"); 95 | }); 96 | 97 | it("Returns two list-items from different lines", () => { 98 | const result = newSubject.process("**Foo.** Bar\nBaz **Quux.** Quz\n"); 99 | assert.equal(result, "- Foo.\n- Quux.\n"); 100 | }); 101 | }); 102 | }); 103 | }); 104 | }); 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /test/ToggleHighlightTest.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import {assert} from 'chai'; 3 | import ToggleHighlight from "../src/ToggleHighlight"; 4 | 5 | let subject: ToggleHighlight = null; 6 | 7 | describe("Toggle Highlights", () => { 8 | before(async () => { 9 | subject = new ToggleHighlight(); 10 | }); 11 | 12 | describe("Empty input", () => { 13 | it("Returns an empty string", () => { 14 | const result = subject.toggleHighlight(""); 15 | assert.equal(result, ""); 16 | }); 17 | }); 18 | 19 | describe("Turning Highlights ON", () => { 20 | it("Returns everything when there is no period", () => { 21 | const result = subject.toggleHighlight("Foo", 0); 22 | assert.equal(result, "==Foo=="); 23 | }); 24 | 25 | it("Returns first highlighted sentence", () => { 26 | const result = subject.toggleHighlight("Foo.", 0); 27 | assert.equal(result, "==Foo.=="); 28 | }); 29 | 30 | it("Returns first highlighted sentence with cursor position 0", () => { 31 | const result = subject.toggleHighlight("Foo. Bar. Baz.", 0); 32 | assert.equal(result, "==Foo.== Bar. Baz."); 33 | }); 34 | 35 | it("Returns first highlighted sentence with cursor position 1", () => { 36 | const result = subject.toggleHighlight("Foo. Bar. Baz.", 1); 37 | assert.equal(result, "==Foo.== Bar. Baz."); 38 | }); 39 | 40 | it("Returns second highlighted sentence with cursor position 6", () => { 41 | const result = subject.toggleHighlight("Foo. Bar. Baz.", 6); 42 | assert.equal(result, "Foo. ==Bar.== Baz."); 43 | }); 44 | 45 | it("Returns second highlighted sentence with cursor position 8", () => { 46 | const result = subject.toggleHighlight("Foo. Bar. Baz.", 8); 47 | assert.equal(result, "Foo. ==Bar.== Baz."); 48 | }); 49 | 50 | it("Returns second highlighted sentence with cursor position 10", () => { 51 | const result = subject.toggleHighlight("==Foo.== Bar. Baz.", 10); 52 | assert.equal(result, "==Foo.== ==Bar.== Baz."); 53 | }); 54 | 55 | }); 56 | 57 | describe("Turning Highlights OFF", () => { 58 | it("Returns sentence", () => { 59 | const result = subject.toggleHighlight("==Foo.==", 2); 60 | assert.equal(result, "Foo."); 61 | }); 62 | 63 | it("Returns first un-highlighted and second sentence", () => { 64 | const result = subject.toggleHighlight("==Foo.== Bar.", 2); 65 | assert.equal(result, "Foo. Bar."); 66 | }); 67 | 68 | it("Returns first sentence un-highlighted", () => { 69 | const result = subject.toggleHighlight("==Foo.== ==Bar.==", 2); 70 | assert.equal(result, "Foo. ==Bar.=="); 71 | }); 72 | 73 | it("Returns second sentence un-highlighted", () => { 74 | const result = subject.toggleHighlight("==Foo.== ==Bar.==", 11); 75 | assert.equal(result, "==Foo.== Bar."); 76 | }); 77 | }); 78 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es5", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6" 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaalias/extract-highlights-plugin/7d16dd5c32b6254c3c8fa3552948129d037b2be1/video.png --------------------------------------------------------------------------------