├── .npmrc ├── .eslintignore ├── versions.json ├── .gitattributes ├── screenshots ├── image-20220601202203.png ├── image20220606011534.png └── image-20220402200431096.png ├── .editorconfig ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── package.json ├── LICENSE ├── esbuild.config.mjs ├── README.md ├── styles.css ├── main.ts └── main.js /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.9.7", 3 | "1.0.1": "0.12.0" 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /screenshots/image-20220601202203.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargrey/obsidian-better-codeblock/HEAD/screenshots/image-20220601202203.png -------------------------------------------------------------------------------- /screenshots/image20220606011534.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargrey/obsidian-better-codeblock/HEAD/screenshots/image20220606011534.png -------------------------------------------------------------------------------- /screenshots/image-20220402200431096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargrey/obsidian-better-codeblock/HEAD/screenshots/image-20220402200431096.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-better-codeblock", 3 | "name": "Better CodeBlock", 4 | "version": "1.0.8", 5 | "minAppVersion": "0.12.0", 6 | "description": "Add title, line number to Obsidian code block", 7 | "author": "StarGrey", 8 | "authorUrl": "https://github.com/stargrey", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "lib": [ 14 | "DOM", 15 | "ES5", 16 | "ES6", 17 | "ES7" 18 | ] 19 | }, 20 | "include": [ 21 | "**/*.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "1.0.1", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "^5.2.0", 17 | "@typescript-eslint/parser": "^5.2.0", 18 | "builtin-modules": "^3.2.0", 19 | "esbuild": "0.13.12", 20 | "obsidian": "latest", 21 | "tslib": "2.3.1", 22 | "typescript": "4.4.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 stargrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/closebrackets', 25 | '@codemirror/collab', 26 | '@codemirror/commands', 27 | '@codemirror/comment', 28 | '@codemirror/fold', 29 | '@codemirror/gutter', 30 | '@codemirror/highlight', 31 | '@codemirror/history', 32 | '@codemirror/language', 33 | '@codemirror/lint', 34 | '@codemirror/matchbrackets', 35 | '@codemirror/panel', 36 | '@codemirror/rangeset', 37 | '@codemirror/rectangular-selection', 38 | '@codemirror/search', 39 | '@codemirror/state', 40 | '@codemirror/stream-parser', 41 | '@codemirror/text', 42 | '@codemirror/tooltip', 43 | '@codemirror/view', 44 | ...builtins], 45 | format: 'cjs', 46 | watch: !prod, 47 | target: 'es2016', 48 | logLevel: "info", 49 | sourcemap: prod ? false : 'inline', 50 | treeShaking: true, 51 | outfile: 'main.js', 52 | }).catch(() => process.exit(1)); 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Obsidian Better Code Block 2 | 3 | This is a plugin for Obsidian (https://obsidian.md). 4 | 5 | Most of the code in this plugin comes from the following two plugins (thanks to their contributions), and the icons are from Admonition. 6 | 7 | https://github.com/tadashi-aikawa/obsidian-embedded-code-title 8 | 9 | https://github.com/nyable/obsidian-code-block-enhancer 10 | 11 | I have merged the code in both plugins and modified some of their functionality. 12 | 13 | ### Features 14 | Enhancer the markdown code block in preview mode. Add title, line number, highlight to code blocks, you can click on the title to collapse or expand the block. 15 | 16 | In version 1.0.5, use the syntax in the diagram below to set the block title, highlight, fold 17 | 18 | - Use `TI:"your title"` to add title 19 | - Use `HL:"numbers"` to add highlight, such as `HL:"1,2,3"`, `HL:"1-3"`, separate by `,` 20 | - Use `"FOLD"` to set the default fold 21 | 22 | If you have a better idea, please submit an issue 23 | 24 | ![image20220606011534.png](screenshots/image20220606011534.png) 25 | 26 | In version 1.0.4, add the language in the top right, like this: 27 | ![screenshots/image-20220601202203.png](screenshots/image-20220601202203.png) 28 | ### Known issues 29 | - Sometimes the auto linefeed error, can be solved by switching the preview mode once 30 | - The PDF export cannot be auto linefeed 31 | ### Manually installing the plugin 32 | 33 | - Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/obsidian-better-codeblock/`. 34 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | 2 | .obsidian-embedded-code-title__code-block-title { 3 | position: absolute !important; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | /* font-size: 85%!important; */ 8 | padding: 3px !important; 9 | padding-left: 15px !important; 10 | margin: 0 !important; 11 | border-radius: 0 !important; 12 | } 13 | 14 | .copy-code-button{ 15 | margin-top: 42px !important; /* 为自带的按钮增加上边距 */ 16 | } 17 | 18 | 19 | pre[class*=language-] { 20 | font-size: var(--editor-font-size); 21 | line-height: 1.5em; 22 | padding-bottom: 0px; 23 | } 24 | .obsidian-embedded-code-title__code-block-title + code[class*=language-]{ 25 | padding: 0em 0em 0em 0em !important; 26 | /* padding-top: 0 !important; */ 27 | font-size: var(--editor-font-size) !important; 28 | line-height: 1.5em !important; 29 | } 30 | /* pre[class*=language-] > code[class*=language-] { 31 | padding: 0em 0em 0em 0.5em !important; 32 | /* padding-top: 0 !important; */ 33 | /* font-size: var(--editor-font-size) !important; 34 | line-height: 1.5em !important; 35 | } */ 36 | 37 | pre[class*=language-].code-block-pre__has-linenum { 38 | padding-left: 3.5em; 39 | } 40 | 41 | .code-block-pre__has-linenum::before { 42 | padding-top: 6px; 43 | } 44 | 45 | /* 代码行号 */ 46 | .code-block-linenum-wrap { 47 | position: absolute; 48 | /* top: 35px; */ 49 | left: 0px; 50 | min-width: 3em; 51 | font-size: var(--editor-font-size); 52 | line-height: 1.5em; 53 | counter-reset: line-num; 54 | text-align: center; 55 | /* border-right: #999 2px solid; 行号与代码间分隔线 */ 56 | user-select: none; 57 | pointer-events: none; 58 | background-color: transparent; 59 | /* background-color: inherit; */ 60 | } 61 | .code-block-linenum-wrap .code-block-linenum { 62 | display: block; 63 | counter-increment: line-num; 64 | pointer-events: none; 65 | } 66 | .code-block-linenum-wrap .code-block-linenum::before { 67 | content: counter(line-num); 68 | } 69 | 70 | /* 代码高亮 */ 71 | pre[class*=language-] .code-block-highlight-wrap { 72 | margin: 0; 73 | padding: 0; 74 | position: absolute; 75 | left: 0px; 76 | top: 35px; 77 | width: 100%; 78 | height: 100%; 79 | background-color: transparent; 80 | pointer-events: none; 81 | } 82 | 83 | pre[class*=language-] .code-block-highlight-wrap span { 84 | display: block; 85 | height: 1.5em; 86 | width: 100%; 87 | } 88 | 89 | /* 折叠代码块 */ 90 | 91 | :root { 92 | --admonition-details-icon: url("data:image/svg+xml;charset=utf-8,"); 93 | } 94 | .obsidian-embedded-code-title__code-block-title{ 95 | line-height: 35px; 96 | height: 35px !important; 97 | color: currentColor !important; 98 | } 99 | 100 | .obsidian-embedded-code-title__code-block-title .langName { 101 | display: inline; 102 | float: right; 103 | line-height: 29px; 104 | margin-right: 35px; 105 | font-weight: bold; 106 | font-size: 14px; 107 | font-family: var(--font-default); 108 | } 109 | 110 | .obsidian-embedded-code-title__code-block-title .collapser { 111 | position: absolute; 112 | top: 50%; 113 | right: 8px; 114 | transform: translateY(-50%); 115 | content: ""; 116 | } 117 | .obsidian-embedded-code-title__code-block-title .collapser .handle { 118 | transform: rotate(90deg); 119 | transition: transform 0.25s; 120 | background-color: currentColor; 121 | -webkit-mask-repeat: no-repeat; 122 | mask-repeat: no-repeat; 123 | -webkit-mask-size: contain; 124 | mask-size: contain; 125 | -webkit-mask-image: var(--admonition-details-icon); 126 | mask-image: var(--admonition-details-icon); 127 | width: 20px; 128 | height: 20px; 129 | } 130 | .obsidian-embedded-code-title__code-block-title[closed] .collapser .handle{ 131 | transform: rotate(0deg); 132 | } 133 | .obsidian-embedded-code-title__code-block-title[closed] + code{ 134 | height: 0; 135 | } 136 | .obsidian-embedded-code-title__code-block-title[closed] + code + span{ 137 | height: 0; 138 | } 139 | 140 | .obsidian-embedded-code-title__code-block-title[closed] + code + span span{ 141 | visibility: hidden; 142 | } 143 | 144 | .obsidian-embedded-code-title__code-block-title[closed] + code + span + span span{ 145 | visibility: hidden; 146 | } 147 | 148 | .obsidian-embedded-code-title__code-block-title > .title { 149 | display: inline-block; 150 | position: relative; 151 | margin-left: 5px !important; 152 | margin: 0; 153 | padding: 0; 154 | 155 | top: 50%; 156 | transform: translateY(-50%); 157 | } 158 | 159 | /* .obsidian-embedded-code-title__code-block-title > .icon-wrap { 160 | display: inline-block; 161 | position: relative; 162 | width: 20px; 163 | height: 20px; 164 | background-position: center; 165 | 166 | top: 50%; 167 | transform: translateY(-50%); 168 | } */ 169 | 170 | .code-block-wrap > pre > code[class*=language-]{ 171 | padding: 0em 0em 0em 0em !important; 172 | /* padding-top: 0 !important; */ 173 | font-size: var(--editor-font-size) !important; 174 | line-height: 1.5em !important; 175 | } -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { linkSync } from 'fs'; 2 | import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, MarkdownPostProcessorContext, Menu, SettingTab, TAbstractFile, TFile, SectionCache, Vault } from 'obsidian'; 3 | import { json } from 'stream/consumers'; 4 | 5 | const DEFAULT_LANG_ATTR = 'language-text' 6 | const DEFAULT_LANG = '' 7 | const LANG_REG = /^language-/ 8 | const LINE_SPLIT_MARK = '\n' 9 | 10 | const titleRegExp = /TI:"([^"]*)"/i 11 | const highLightLinesRegExp = /HL:"([^"]*)"/i 12 | const foldRegExp = /"FOLD"/i 13 | 14 | const CB_PADDING_TOP = "35px" // 代码块上边距 15 | 16 | interface Settings { 17 | substitutionTokenForSpace: string; 18 | titleBackgroundColor: string; 19 | titleFontColor: string; 20 | highLightColor: string; 21 | 22 | excludeLangs: string[]; // 需要排除的语言 23 | 24 | showLineNumber: boolean; // 显示行号 25 | showDividingLine: boolean; 26 | showLangNameInTopRight: boolean; 27 | } 28 | 29 | const DEFAULT_SETTINGS: Settings = { 30 | substitutionTokenForSpace: undefined, 31 | titleBackgroundColor: "#00000020", 32 | titleFontColor: undefined, 33 | highLightColor: "#2d82cc20", 34 | 35 | excludeLangs: [], 36 | 37 | showLineNumber: true, 38 | showDividingLine: false, 39 | showLangNameInTopRight: true 40 | }; 41 | 42 | interface CodeBlockMeta { 43 | // Language name 44 | langName: string; 45 | 46 | // Code block total line size 47 | lineSize: number; 48 | 49 | // Code block 'pre' HTMLElement 50 | pre: HTMLElement; 51 | 52 | // Code block 'code' HTMLElement 53 | code: HTMLElement; 54 | 55 | title: string; // 代码块标题 56 | isCollapse:boolean; // 是否默认折叠 57 | 58 | // Code block wrap div 59 | div: HTMLElement; 60 | contentList: string[]; 61 | highLightLines: number[]; 62 | } 63 | 64 | // Refer https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#escaping 65 | function escapeRegExp(str: string): string { 66 | return str.replace(/[.*+?^=!:${}()|[\]\/\\]/g, "\\$&"); // 为特殊符号加上转义符号"\" 67 | } 68 | 69 | export default class BetterCodeBlock extends Plugin { 70 | settings: Settings; 71 | 72 | async onload() { 73 | console.log("Loading Better Code Block Plugin"); 74 | await this.loadSettings(); 75 | this.addSettingTab(new BetterCodeBlockTab(this.app, this)); 76 | this.registerMarkdownPostProcessor((el, ctx) => { 77 | BetterCodeBlocks(el, ctx, this) 78 | app.workspace.on('resize', () => { 79 | resizeNumWrapAndHLWrap(el, ctx) 80 | }) 81 | }) 82 | 83 | } 84 | 85 | onunload () { 86 | console.log('Unloading Better Code Block Plugin'); 87 | } 88 | 89 | async loadSettings() { 90 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 91 | } 92 | 93 | async saveSettings() { 94 | await this.saveData(this.settings); 95 | } 96 | } 97 | 98 | class BetterCodeBlockTab extends PluginSettingTab { 99 | plugin: BetterCodeBlock; 100 | 101 | constructor(app: App, plugin: BetterCodeBlock) { 102 | super(app, plugin); 103 | this.plugin = plugin; 104 | } 105 | 106 | display(): void { 107 | let { containerEl } = this; 108 | 109 | containerEl.empty(); 110 | 111 | new Setting(containerEl) 112 | .setName("Exclude language list") 113 | .setDesc("Title and line numbers do not apply in these languages, separate by `,`") 114 | .addText(text => text.setPlaceholder('like todoist,other,...') 115 | .setValue(this.plugin.settings.excludeLangs.join(',')) 116 | .onChange(async (value) => { 117 | this.plugin.settings.excludeLangs = value.split(','); 118 | await this.plugin.saveSettings(); 119 | }) 120 | ) 121 | 122 | new Setting(containerEl).setName("Font color of title").addText((tc) => 123 | tc 124 | .setPlaceholder("Enter a color") 125 | .setValue(this.plugin.settings.titleFontColor) 126 | .onChange(async (value) => { 127 | this.plugin.settings.titleFontColor = value; 128 | await this.plugin.saveSettings(); 129 | }) 130 | ); 131 | 132 | new Setting(containerEl) 133 | .setName("Background color of title") 134 | .addText((tc) => 135 | tc 136 | .setPlaceholder("#00000020") 137 | .setValue(this.plugin.settings.titleBackgroundColor) 138 | .onChange(async (value) => { 139 | this.plugin.settings.titleBackgroundColor = value; 140 | await this.plugin.saveSettings(); 141 | }) 142 | ); 143 | 144 | new Setting(containerEl) 145 | .setName("HighLight Color") 146 | .addText((tc) => 147 | tc 148 | .setPlaceholder("#2d82cc20") 149 | .setValue(this.plugin.settings.highLightColor) 150 | .onChange(async (value) => { 151 | this.plugin.settings.highLightColor = value; 152 | await this.plugin.saveSettings(); 153 | }) 154 | ); 155 | 156 | new Setting(containerEl) 157 | .setName("Show line number") 158 | .addToggle((tc) => 159 | tc.setValue(this.plugin.settings.showLineNumber) 160 | .onChange(async(value) => { 161 | this.plugin.settings.showLineNumber = value; 162 | await this.plugin.saveSettings(); 163 | }) 164 | ) 165 | 166 | new Setting(containerEl) 167 | .setName("Show dividing line") 168 | .addToggle((tc) => 169 | tc.setValue(this.plugin.settings.showDividingLine) 170 | .onChange(async(value) => { 171 | this.plugin.settings.showDividingLine = value; 172 | await this.plugin.saveSettings(); 173 | }) 174 | ) 175 | 176 | new Setting(containerEl) 177 | .setName("Show language name in the top right") 178 | .addToggle((tc) => 179 | tc.setValue(this.plugin.settings.showLangNameInTopRight) 180 | .onChange(async(value) => { 181 | this.plugin.settings.showLangNameInTopRight = value; 182 | await this.plugin.saveSettings(); 183 | }) 184 | ) 185 | } 186 | } 187 | 188 | 189 | export async function BetterCodeBlocks(el: HTMLElement, context: MarkdownPostProcessorContext, plugin: BetterCodeBlock) { 190 | const settings = plugin.settings 191 | const codeElm: HTMLElement = el.querySelector('pre > code') 192 | // only change pre>code 193 | if (!codeElm) { return } 194 | 195 | let lang = DEFAULT_LANG 196 | // return when lang is in exclude list 197 | if (plugin.settings.excludeLangs.some(eLangName => codeElm.classList.contains(`language-${eLangName}`))) { 198 | return 199 | } 200 | 201 | codeElm.classList.forEach((value, key, parent) => { 202 | if (LANG_REG.test(value)) { 203 | lang = value.replace('language-', '') 204 | return 205 | } 206 | }) 207 | 208 | // if the code block is not described, return 209 | if(lang == DEFAULT_LANG) { 210 | return 211 | } 212 | 213 | let codeBlock = context.getSectionInfo(codeElm) 214 | let codeBlockFirstLine = "" 215 | 216 | if(codeBlock) { 217 | let view = app.workspace.getActiveViewOfType(MarkdownView) 218 | codeBlockFirstLine = view.editor.getLine(codeBlock.lineStart) 219 | } else { 220 | let file = app.vault.getAbstractFileByPath(context.sourcePath) 221 | let cache = app.metadataCache.getCache(context.sourcePath) 222 | let fileContent = await app.vault.cachedRead( file) 223 | let fileContentLines = fileContent.split(/\n/g) 224 | 225 | let codeBlockFirstLines: string[] = [] 226 | let codeBlockSections: SectionCache[] = [] 227 | 228 | cache.sections?.forEach(async element => { 229 | if(element.type == "code") { 230 | let lineStart = element.position.start.line 231 | codeBlockFirstLine = fileContentLines[lineStart] 232 | codeBlockSections.push(element) 233 | codeBlockFirstLines.push(codeBlockFirstLine) 234 | } 235 | }); 236 | exportPDF(el, plugin, codeBlockFirstLines, codeBlockSections) 237 | return 238 | } 239 | 240 | let title: string = "" 241 | let highLightLines: number[] = [] 242 | if(codeBlockFirstLine.match(titleRegExp) != null) { 243 | title = codeBlockFirstLine.match(titleRegExp)[1] 244 | } 245 | if(codeBlockFirstLine.match(highLightLinesRegExp) != null) { 246 | let highLightLinesInfo = codeBlockFirstLine.match(highLightLinesRegExp)[1] 247 | highLightLines = analyseHighLightLines(highLightLinesInfo) 248 | } 249 | 250 | let isCollapse = false; 251 | if(foldRegExp.test(codeBlockFirstLine)) { 252 | isCollapse = true 253 | } 254 | 255 | const pre = codeElm.parentElement // code-block-pre__has-linenum 256 | const div = pre.parentElement // class code-block-wrap 257 | 258 | /* const { lineStart, lineEnd } = ctx.getSectionInfo(el) 259 | const lineSize = lineEnd - lineStart - 1 */ 260 | const contentList: string[] = codeElm.textContent.split(LINE_SPLIT_MARK) 261 | // const lineSize = contentList.length - 1 262 | const lineSize = codeBlock.lineEnd - codeBlock.lineStart - 1 263 | 264 | const cbMeta = { langName: lang, lineSize, pre, code: codeElm, title, isCollapse, div, contentList, highLightLines} 265 | 266 | const {showLineNumber} = plugin.settings 267 | 268 | addCodeTitleWrapper(plugin, pre, cbMeta) 269 | //addIconToTitle(plugin, pre, cbMeta) 270 | addCodeTitle(plugin, pre, cbMeta); 271 | 272 | // add line number 273 | if (showLineNumber) { 274 | addLineNumber(plugin, cbMeta) 275 | } 276 | 277 | addLineHighLight(plugin, pre, cbMeta) 278 | 279 | resizeNumWrapAndHLWrap(el,context) // 调用一次以解决某些时候打开文件行高未被重设高度 280 | } 281 | 282 | function createElement (tagName: string, defaultClassName?: string) { 283 | const element = document.createElement(tagName) 284 | if (defaultClassName) { 285 | element.className = defaultClassName 286 | } 287 | return element 288 | } 289 | 290 | function addCodeTitleWrapper(plugin: BetterCodeBlock, preElm: HTMLElement, cbMeta: CodeBlockMeta) { 291 | preElm.style.setProperty("position", "relative", "important"); 292 | preElm.style.setProperty("padding-top", CB_PADDING_TOP, "important"); 293 | 294 | let wrapper = document.createElement("pre") 295 | if(cbMeta.isCollapse) { 296 | wrapper.setAttribute("closed","") 297 | } 298 | wrapper.className = "obsidian-embedded-code-title__code-block-title" 299 | 300 | wrapper.style.backgroundColor = plugin.settings.titleBackgroundColor || "#00000020"; 301 | 302 | let collapser = createElement("div","collapser") 303 | let handle = createElement("div", "handle") 304 | collapser.appendChild(handle) 305 | wrapper.appendChild(collapser) 306 | 307 | wrapper.addEventListener('click',function(this: any) { 308 | if(wrapper.hasAttribute("closed")){ 309 | wrapper.removeAttribute("closed") 310 | } else { 311 | wrapper.setAttribute("closed",'') 312 | } 313 | }) 314 | 315 | preElm.appendChild(wrapper) 316 | } 317 | 318 | function addCodeTitle (plugin: BetterCodeBlock, preElm: HTMLElement, cbMeta: CodeBlockMeta) { 319 | let wrapper = preElm.querySelector(".obsidian-embedded-code-title__code-block-title") 320 | 321 | let titleElm = document.createElement("div") 322 | titleElm.className = "title" 323 | 324 | titleElm.appendText(cbMeta.title) 325 | wrapper.appendChild(titleElm) 326 | 327 | if(plugin.settings.titleFontColor) { 328 | titleElm.style.setProperty("color", plugin.settings.titleFontColor, "important") 329 | } 330 | 331 | if(plugin.settings.showLangNameInTopRight) { 332 | let langName = document.createElement("div"); // 在右侧添加代码类型 333 | let langNameString = cbMeta.langName 334 | langNameString = langNameString[0].toUpperCase() + langNameString.slice(1) // 首字母大写 335 | langName.appendText(langNameString); 336 | langName.className = "langName"; 337 | wrapper.appendChild(langName); 338 | } 339 | 340 | preElm.prepend(wrapper); 341 | 342 | } 343 | 344 | function addLineNumber (plugin: BetterCodeBlock, cbMeta: CodeBlockMeta) { 345 | const { lineSize, pre, div } = cbMeta 346 | // let div position: relative; 347 | div.classList.add('code-block-wrap') 348 | 349 | // const { fontSize, lineHeight } = window.getComputedStyle(cbMeta.code) 350 | const lineNumber = createElement('span', 'code-block-linenum-wrap') 351 | lineNumber.style.top = CB_PADDING_TOP; 352 | Array.from({ length: lineSize }, (v, k) => k).forEach(i => { 353 | const singleLine = createElement('span', 'code-block-linenum') 354 | // singleLine.style.fontSize = fontSize 355 | // singleLine.style.lineHeight = lineHeight 356 | lineNumber.appendChild(singleLine) 357 | }) 358 | 359 | if(plugin.settings.showDividingLine) { 360 | lineNumber.style.borderRight = "1px currentColor solid" 361 | } 362 | 363 | pre.appendChild(lineNumber) 364 | pre.classList.add('code-block-pre__has-linenum') 365 | } 366 | 367 | function addLineHighLight(plugin: BetterCodeBlock, preElm: HTMLElement, cbMeta: CodeBlockMeta) { 368 | if(cbMeta.highLightLines.length == 0) return 369 | 370 | let highLightWrap = document.createElement("pre") 371 | highLightWrap.className = "code-block-highlight-wrap" 372 | for(let i = 0; i < cbMeta.lineSize; i++) { 373 | const singleLine = createElement("span", 'code-block-highlight') 374 | if(cbMeta.highLightLines.contains(i+1)) { 375 | singleLine.style.backgroundColor = plugin.settings.highLightColor || "#2d82cc20" 376 | } 377 | highLightWrap.appendChild(singleLine) 378 | } 379 | 380 | preElm.appendChild(highLightWrap) 381 | } 382 | 383 | function analyseHighLightLines(str: string): number[] { 384 | str = str.replace(/\s*/g, "") // 去除字符串中所有空格 385 | const result: number[] = [] 386 | 387 | let strs = str.split(",") 388 | strs.forEach(it => { 389 | if(/\w+-\w+/.test(it)) { // 如果匹配 1-3 这样的格式,依次添加数字 390 | let left = Number(it.split('-')[0]) 391 | let right = Number(it.split('-')[1]) 392 | for(let i = left; i <= right; i++) { 393 | result.push(i) 394 | } 395 | } else { 396 | result.push(Number(it)) 397 | } 398 | }) 399 | 400 | return result 401 | } 402 | 403 | function addIconToTitle(plugin: BetterCodeBlock, preElm: HTMLElement, cbMeta: CodeBlockMeta) { 404 | let title = preElm.querySelectorAll(".obsidian-embedded-code-title__code-block-title") 405 | 406 | title.forEach(it => { 407 | let iconWrap = createElement("div","icon-wrap") 408 | let icon = document.createElement("img") 409 | icon.src = "" 410 | iconWrap.appendChild(icon) 411 | it.appendChild(iconWrap) 412 | }) 413 | 414 | } 415 | 416 | // 在自动换行时对数字和高亮行重新设置高度 417 | // These codes refer to the https://github.com/lijyze/obsidian-advanced-codeblock 418 | function resizeNumWrapAndHLWrap(el: HTMLElement, context: MarkdownPostProcessorContext) { 419 | setTimeout(async function(){ // 延时100毫秒以解决某些时候打开文件行高未被重设高度 420 | // console.log('on resize') 421 | let codeBlockEl : HTMLElement = el.querySelector('pre > code') 422 | if(!codeBlockEl) return 423 | 424 | let numWrap = el.querySelector('.code-block-linenum-wrap') 425 | let highWrap = el.querySelector('.code-block-highlight-wrap') 426 | 427 | let codeBlockInfo = context.getSectionInfo(codeBlockEl) 428 | // let view = app.workspace.getActiveViewOfType(MarkdownView) 429 | // let codeBlockLineNum = codeBlockInfo.lineEnd - codeBlockInfo.lineStart - 1 // 除去首尾两行 430 | let view 431 | let codeBlockLineNum 432 | 433 | let lineStart = 0 434 | let lineEnd = 0 435 | if(codeBlockInfo) { 436 | view = app.workspace.getActiveViewOfType(MarkdownView) 437 | codeBlockLineNum = codeBlockInfo.lineEnd - codeBlockInfo.lineStart - 1 // 除去首尾两行 438 | } else { 439 | return 440 | // let file = app.vault.getAbstractFileByPath(context.sourcePath) 441 | // let cache = app.metadataCache.getCache(context.sourcePath) 442 | 443 | // cache.sections?.forEach(async element => { 444 | // if(element.type == "code") { 445 | // lineStart = element.position.start.line 446 | // lineEnd = element.position.end.line 447 | // codeBlockLineNum = lineEnd - lineStart - 1 448 | // return 449 | // } 450 | // }); 451 | // let file = app.vault.getAbstractFileByPath(context.sourcePath) 452 | // let cache = app.metadataCache.getCache(context.sourcePath) 453 | // let fileContent = await app.vault.cachedRead( file) 454 | // let fileContentLines = fileContent.split(/\n/g) 455 | } 456 | 457 | let span = createElement("span") 458 | 459 | for(let i = 0; i < codeBlockLineNum; i++) { 460 | let oneLineText 461 | if(view){ 462 | oneLineText = view.editor.getLine(codeBlockInfo.lineStart + i + 1) 463 | } else { 464 | // oneLineText = fileContentLines[lineStart + 1 + i] 465 | // let file = app.vault.getAbstractFileByPath(context.sourcePath) 466 | // let cache = app.metadataCache.getCache(context.sourcePath) 467 | // let fileContent = await app.vault.cachedRead( file) 468 | // let fileContentLines = fileContent.split(/\n/g) 469 | // oneLineText = fileContentLines[cache.sections] 470 | } 471 | span.innerHTML = oneLineText || "0" 472 | 473 | codeBlockEl.appendChild(span) 474 | span.style.display = 'block' 475 | 476 | let lineHeight = span.getBoundingClientRect().height + 'px' // 测量本行文字的高度 477 | 478 | // console.log(lineHeight + ' ' + span.getBoundingClientRect().width); 479 | 480 | let numOneLine = numWrap? numWrap.childNodes[i] as HTMLElement : null 481 | let hlOneLine = highWrap? highWrap.childNodes[i] as HTMLElement : null 482 | 483 | if(numOneLine) numOneLine.style.height = lineHeight; 484 | if(hlOneLine) hlOneLine.style.height = lineHeight; 485 | 486 | span.remove() // 测量完后删掉 487 | } 488 | }, 100) 489 | } 490 | 491 | function exportPDF(el: HTMLElement, plugin: BetterCodeBlock, codeBlockFirstLines: string[], codeBlockSections: SectionCache[]) { 492 | let codeBlocks = el.querySelectorAll('pre > code') 493 | codeBlocks.forEach((codeElm, key) => { 494 | let langName = "", title = "", highLightLines: number[] = [] 495 | codeElm.classList.forEach(value => { 496 | if(LANG_REG.test(value)) { 497 | langName = value.replace('language-', '') 498 | return 499 | } 500 | }) 501 | 502 | if(codeBlockFirstLines[key].match(titleRegExp) != null) { 503 | title = codeBlockFirstLines[key].match(titleRegExp)[1] 504 | } 505 | if(codeBlockFirstLines[key].match(highLightLinesRegExp) != null) { 506 | let highLightLinesInfo = codeBlockFirstLines[key].match(highLightLinesRegExp)[1] 507 | highLightLines = analyseHighLightLines(highLightLinesInfo) 508 | } 509 | 510 | let lineSize = codeBlockSections[key].position.end.line - codeBlockSections[key].position.start.line - 1 511 | 512 | let cbMeta: CodeBlockMeta = { 513 | langName: langName, 514 | lineSize: lineSize, 515 | pre: codeElm.parentElement, 516 | code: codeElm as HTMLElement, 517 | title: title, 518 | isCollapse: false, 519 | div: codeElm.parentElement.parentElement, 520 | contentList: [], 521 | highLightLines: highLightLines 522 | } 523 | addCodeTitleWrapper(plugin, codeElm.parentElement, cbMeta) // 导出取消代码块折叠 524 | addCodeTitle(plugin, cbMeta.pre, cbMeta) 525 | if(plugin.settings.showLineNumber) { 526 | addLineNumber(plugin, cbMeta) 527 | } 528 | addLineHighLight(plugin, cbMeta.pre, cbMeta) 529 | }) 530 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 3 | if you want to view the source, please visit the github repository of this plugin 4 | */ 5 | 6 | var __create = Object.create; 7 | var __defProp = Object.defineProperty; 8 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 9 | var __getOwnPropNames = Object.getOwnPropertyNames; 10 | var __getProtoOf = Object.getPrototypeOf; 11 | var __hasOwnProp = Object.prototype.hasOwnProperty; 12 | var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); 13 | var __export = (target, all) => { 14 | __markAsModule(target); 15 | for (var name in all) 16 | __defProp(target, name, { get: all[name], enumerable: true }); 17 | }; 18 | var __reExport = (target, module2, desc) => { 19 | if (module2 && typeof module2 === "object" || typeof module2 === "function") { 20 | for (let key of __getOwnPropNames(module2)) 21 | if (!__hasOwnProp.call(target, key) && key !== "default") 22 | __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); 23 | } 24 | return target; 25 | }; 26 | var __toModule = (module2) => { 27 | return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); 28 | }; 29 | var __async = (__this, __arguments, generator) => { 30 | return new Promise((resolve, reject) => { 31 | var fulfilled = (value) => { 32 | try { 33 | step(generator.next(value)); 34 | } catch (e) { 35 | reject(e); 36 | } 37 | }; 38 | var rejected = (value) => { 39 | try { 40 | step(generator.throw(value)); 41 | } catch (e) { 42 | reject(e); 43 | } 44 | }; 45 | var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); 46 | step((generator = generator.apply(__this, __arguments)).next()); 47 | }); 48 | }; 49 | 50 | // main.ts 51 | __export(exports, { 52 | BetterCodeBlocks: () => BetterCodeBlocks, 53 | default: () => BetterCodeBlock 54 | }); 55 | var import_obsidian = __toModule(require("obsidian")); 56 | var DEFAULT_LANG = ""; 57 | var LANG_REG = /^language-/; 58 | var LINE_SPLIT_MARK = "\n"; 59 | var titleRegExp = /TI:"([^"]*)"/i; 60 | var highLightLinesRegExp = /HL:"([^"]*)"/i; 61 | var foldRegExp = /"FOLD"/i; 62 | var CB_PADDING_TOP = "35px"; 63 | var DEFAULT_SETTINGS = { 64 | substitutionTokenForSpace: void 0, 65 | titleBackgroundColor: "#00000020", 66 | titleFontColor: void 0, 67 | highLightColor: "#2d82cc20", 68 | excludeLangs: [], 69 | showLineNumber: true, 70 | showDividingLine: false, 71 | showLangNameInTopRight: true 72 | }; 73 | var BetterCodeBlock = class extends import_obsidian.Plugin { 74 | onload() { 75 | return __async(this, null, function* () { 76 | console.log("Loading Better Code Block Plugin"); 77 | yield this.loadSettings(); 78 | this.addSettingTab(new BetterCodeBlockTab(this.app, this)); 79 | this.registerMarkdownPostProcessor((el, ctx) => { 80 | BetterCodeBlocks(el, ctx, this); 81 | app.workspace.on("resize", () => { 82 | resizeNumWrapAndHLWrap(el, ctx); 83 | }); 84 | }); 85 | }); 86 | } 87 | onunload() { 88 | console.log("Unloading Better Code Block Plugin"); 89 | } 90 | loadSettings() { 91 | return __async(this, null, function* () { 92 | this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); 93 | }); 94 | } 95 | saveSettings() { 96 | return __async(this, null, function* () { 97 | yield this.saveData(this.settings); 98 | }); 99 | } 100 | }; 101 | var BetterCodeBlockTab = class extends import_obsidian.PluginSettingTab { 102 | constructor(app2, plugin) { 103 | super(app2, plugin); 104 | this.plugin = plugin; 105 | } 106 | display() { 107 | let { containerEl } = this; 108 | containerEl.empty(); 109 | new import_obsidian.Setting(containerEl).setName("Exclude language list").setDesc("Title and line numbers do not apply in these languages, separate by `,`").addText((text) => text.setPlaceholder("like todoist,other,...").setValue(this.plugin.settings.excludeLangs.join(",")).onChange((value) => __async(this, null, function* () { 110 | this.plugin.settings.excludeLangs = value.split(","); 111 | yield this.plugin.saveSettings(); 112 | }))); 113 | new import_obsidian.Setting(containerEl).setName("Font color of title").addText((tc) => tc.setPlaceholder("Enter a color").setValue(this.plugin.settings.titleFontColor).onChange((value) => __async(this, null, function* () { 114 | this.plugin.settings.titleFontColor = value; 115 | yield this.plugin.saveSettings(); 116 | }))); 117 | new import_obsidian.Setting(containerEl).setName("Background color of title").addText((tc) => tc.setPlaceholder("#00000020").setValue(this.plugin.settings.titleBackgroundColor).onChange((value) => __async(this, null, function* () { 118 | this.plugin.settings.titleBackgroundColor = value; 119 | yield this.plugin.saveSettings(); 120 | }))); 121 | new import_obsidian.Setting(containerEl).setName("HighLight Color").addText((tc) => tc.setPlaceholder("#2d82cc20").setValue(this.plugin.settings.highLightColor).onChange((value) => __async(this, null, function* () { 122 | this.plugin.settings.highLightColor = value; 123 | yield this.plugin.saveSettings(); 124 | }))); 125 | new import_obsidian.Setting(containerEl).setName("Show line number").addToggle((tc) => tc.setValue(this.plugin.settings.showLineNumber).onChange((value) => __async(this, null, function* () { 126 | this.plugin.settings.showLineNumber = value; 127 | yield this.plugin.saveSettings(); 128 | }))); 129 | new import_obsidian.Setting(containerEl).setName("Show dividing line").addToggle((tc) => tc.setValue(this.plugin.settings.showDividingLine).onChange((value) => __async(this, null, function* () { 130 | this.plugin.settings.showDividingLine = value; 131 | yield this.plugin.saveSettings(); 132 | }))); 133 | new import_obsidian.Setting(containerEl).setName("Show language name in the top right").addToggle((tc) => tc.setValue(this.plugin.settings.showLangNameInTopRight).onChange((value) => __async(this, null, function* () { 134 | this.plugin.settings.showLangNameInTopRight = value; 135 | yield this.plugin.saveSettings(); 136 | }))); 137 | } 138 | }; 139 | function BetterCodeBlocks(el, context, plugin) { 140 | return __async(this, null, function* () { 141 | var _a; 142 | const settings = plugin.settings; 143 | const codeElm = el.querySelector("pre > code"); 144 | if (!codeElm) { 145 | return; 146 | } 147 | let lang = DEFAULT_LANG; 148 | if (plugin.settings.excludeLangs.some((eLangName) => codeElm.classList.contains(`language-${eLangName}`))) { 149 | return; 150 | } 151 | codeElm.classList.forEach((value, key, parent) => { 152 | if (LANG_REG.test(value)) { 153 | lang = value.replace("language-", ""); 154 | return; 155 | } 156 | }); 157 | if (lang == DEFAULT_LANG) { 158 | return; 159 | } 160 | let codeBlock = context.getSectionInfo(codeElm); 161 | let codeBlockFirstLine = ""; 162 | if (codeBlock) { 163 | let view = app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); 164 | codeBlockFirstLine = view.editor.getLine(codeBlock.lineStart); 165 | } else { 166 | let file = app.vault.getAbstractFileByPath(context.sourcePath); 167 | let cache = app.metadataCache.getCache(context.sourcePath); 168 | let fileContent = yield app.vault.cachedRead(file); 169 | let fileContentLines = fileContent.split(/\n/g); 170 | let codeBlockFirstLines = []; 171 | let codeBlockSections = []; 172 | (_a = cache.sections) == null ? void 0 : _a.forEach((element) => __async(this, null, function* () { 173 | if (element.type == "code") { 174 | let lineStart = element.position.start.line; 175 | codeBlockFirstLine = fileContentLines[lineStart]; 176 | codeBlockSections.push(element); 177 | codeBlockFirstLines.push(codeBlockFirstLine); 178 | } 179 | })); 180 | exportPDF(el, plugin, codeBlockFirstLines, codeBlockSections); 181 | return; 182 | } 183 | let title = ""; 184 | let highLightLines = []; 185 | if (codeBlockFirstLine.match(titleRegExp) != null) { 186 | title = codeBlockFirstLine.match(titleRegExp)[1]; 187 | } 188 | if (codeBlockFirstLine.match(highLightLinesRegExp) != null) { 189 | let highLightLinesInfo = codeBlockFirstLine.match(highLightLinesRegExp)[1]; 190 | highLightLines = analyseHighLightLines(highLightLinesInfo); 191 | } 192 | let isCollapse = false; 193 | if (foldRegExp.test(codeBlockFirstLine)) { 194 | isCollapse = true; 195 | } 196 | const pre = codeElm.parentElement; 197 | const div = pre.parentElement; 198 | const contentList = codeElm.textContent.split(LINE_SPLIT_MARK); 199 | const lineSize = codeBlock.lineEnd - codeBlock.lineStart - 1; 200 | const cbMeta = { langName: lang, lineSize, pre, code: codeElm, title, isCollapse, div, contentList, highLightLines }; 201 | const { showLineNumber } = plugin.settings; 202 | addCodeTitleWrapper(plugin, pre, cbMeta); 203 | addCodeTitle(plugin, pre, cbMeta); 204 | if (showLineNumber) { 205 | addLineNumber(plugin, cbMeta); 206 | } 207 | addLineHighLight(plugin, pre, cbMeta); 208 | resizeNumWrapAndHLWrap(el, context); 209 | }); 210 | } 211 | function createElement(tagName, defaultClassName) { 212 | const element = document.createElement(tagName); 213 | if (defaultClassName) { 214 | element.className = defaultClassName; 215 | } 216 | return element; 217 | } 218 | function addCodeTitleWrapper(plugin, preElm, cbMeta) { 219 | preElm.style.setProperty("position", "relative", "important"); 220 | preElm.style.setProperty("padding-top", CB_PADDING_TOP, "important"); 221 | let wrapper = document.createElement("pre"); 222 | if (cbMeta.isCollapse) { 223 | wrapper.setAttribute("closed", ""); 224 | } 225 | wrapper.className = "obsidian-embedded-code-title__code-block-title"; 226 | wrapper.style.backgroundColor = plugin.settings.titleBackgroundColor || "#00000020"; 227 | let collapser = createElement("div", "collapser"); 228 | let handle = createElement("div", "handle"); 229 | collapser.appendChild(handle); 230 | wrapper.appendChild(collapser); 231 | wrapper.addEventListener("click", function() { 232 | if (wrapper.hasAttribute("closed")) { 233 | wrapper.removeAttribute("closed"); 234 | } else { 235 | wrapper.setAttribute("closed", ""); 236 | } 237 | }); 238 | preElm.appendChild(wrapper); 239 | } 240 | function addCodeTitle(plugin, preElm, cbMeta) { 241 | let wrapper = preElm.querySelector(".obsidian-embedded-code-title__code-block-title"); 242 | let titleElm = document.createElement("div"); 243 | titleElm.className = "title"; 244 | titleElm.appendText(cbMeta.title); 245 | wrapper.appendChild(titleElm); 246 | if (plugin.settings.titleFontColor) { 247 | titleElm.style.setProperty("color", plugin.settings.titleFontColor, "important"); 248 | } 249 | if (plugin.settings.showLangNameInTopRight) { 250 | let langName = document.createElement("div"); 251 | let langNameString = cbMeta.langName; 252 | langNameString = langNameString[0].toUpperCase() + langNameString.slice(1); 253 | langName.appendText(langNameString); 254 | langName.className = "langName"; 255 | wrapper.appendChild(langName); 256 | } 257 | preElm.prepend(wrapper); 258 | } 259 | function addLineNumber(plugin, cbMeta) { 260 | const { lineSize, pre, div } = cbMeta; 261 | div.classList.add("code-block-wrap"); 262 | const lineNumber = createElement("span", "code-block-linenum-wrap"); 263 | lineNumber.style.top = CB_PADDING_TOP; 264 | Array.from({ length: lineSize }, (v, k) => k).forEach((i) => { 265 | const singleLine = createElement("span", "code-block-linenum"); 266 | lineNumber.appendChild(singleLine); 267 | }); 268 | if (plugin.settings.showDividingLine) { 269 | lineNumber.style.borderRight = "1px currentColor solid"; 270 | } 271 | pre.appendChild(lineNumber); 272 | pre.classList.add("code-block-pre__has-linenum"); 273 | } 274 | function addLineHighLight(plugin, preElm, cbMeta) { 275 | if (cbMeta.highLightLines.length == 0) 276 | return; 277 | let highLightWrap = document.createElement("pre"); 278 | highLightWrap.className = "code-block-highlight-wrap"; 279 | for (let i = 0; i < cbMeta.lineSize; i++) { 280 | const singleLine = createElement("span", "code-block-highlight"); 281 | if (cbMeta.highLightLines.contains(i + 1)) { 282 | singleLine.style.backgroundColor = plugin.settings.highLightColor || "#2d82cc20"; 283 | } 284 | highLightWrap.appendChild(singleLine); 285 | } 286 | preElm.appendChild(highLightWrap); 287 | } 288 | function analyseHighLightLines(str) { 289 | str = str.replace(/\s*/g, ""); 290 | const result = []; 291 | let strs = str.split(","); 292 | strs.forEach((it) => { 293 | if (/\w+-\w+/.test(it)) { 294 | let left = Number(it.split("-")[0]); 295 | let right = Number(it.split("-")[1]); 296 | for (let i = left; i <= right; i++) { 297 | result.push(i); 298 | } 299 | } else { 300 | result.push(Number(it)); 301 | } 302 | }); 303 | return result; 304 | } 305 | function resizeNumWrapAndHLWrap(el, context) { 306 | setTimeout(function() { 307 | return __async(this, null, function* () { 308 | let codeBlockEl = el.querySelector("pre > code"); 309 | if (!codeBlockEl) 310 | return; 311 | let numWrap = el.querySelector(".code-block-linenum-wrap"); 312 | let highWrap = el.querySelector(".code-block-highlight-wrap"); 313 | let codeBlockInfo = context.getSectionInfo(codeBlockEl); 314 | let view; 315 | let codeBlockLineNum; 316 | let lineStart = 0; 317 | let lineEnd = 0; 318 | if (codeBlockInfo) { 319 | view = app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); 320 | codeBlockLineNum = codeBlockInfo.lineEnd - codeBlockInfo.lineStart - 1; 321 | } else { 322 | return; 323 | } 324 | let span = createElement("span"); 325 | for (let i = 0; i < codeBlockLineNum; i++) { 326 | let oneLineText; 327 | if (view) { 328 | oneLineText = view.editor.getLine(codeBlockInfo.lineStart + i + 1); 329 | } else { 330 | } 331 | span.innerHTML = oneLineText || "0"; 332 | codeBlockEl.appendChild(span); 333 | span.style.display = "block"; 334 | let lineHeight = span.getBoundingClientRect().height + "px"; 335 | let numOneLine = numWrap ? numWrap.childNodes[i] : null; 336 | let hlOneLine = highWrap ? highWrap.childNodes[i] : null; 337 | if (numOneLine) 338 | numOneLine.style.height = lineHeight; 339 | if (hlOneLine) 340 | hlOneLine.style.height = lineHeight; 341 | span.remove(); 342 | } 343 | }); 344 | }, 100); 345 | } 346 | function exportPDF(el, plugin, codeBlockFirstLines, codeBlockSections) { 347 | let codeBlocks = el.querySelectorAll("pre > code"); 348 | codeBlocks.forEach((codeElm, key) => { 349 | let langName = "", title = "", highLightLines = []; 350 | codeElm.classList.forEach((value) => { 351 | if (LANG_REG.test(value)) { 352 | langName = value.replace("language-", ""); 353 | return; 354 | } 355 | }); 356 | if (codeBlockFirstLines[key].match(titleRegExp) != null) { 357 | title = codeBlockFirstLines[key].match(titleRegExp)[1]; 358 | } 359 | if (codeBlockFirstLines[key].match(highLightLinesRegExp) != null) { 360 | let highLightLinesInfo = codeBlockFirstLines[key].match(highLightLinesRegExp)[1]; 361 | highLightLines = analyseHighLightLines(highLightLinesInfo); 362 | } 363 | let lineSize = codeBlockSections[key].position.end.line - codeBlockSections[key].position.start.line - 1; 364 | let cbMeta = { 365 | langName, 366 | lineSize, 367 | pre: codeElm.parentElement, 368 | code: codeElm, 369 | title, 370 | isCollapse: false, 371 | div: codeElm.parentElement.parentElement, 372 | contentList: [], 373 | highLightLines 374 | }; 375 | addCodeTitleWrapper(plugin, codeElm.parentElement, cbMeta); 376 | addCodeTitle(plugin, cbMeta.pre, cbMeta); 377 | if (plugin.settings.showLineNumber) { 378 | addLineNumber(plugin, cbMeta); 379 | } 380 | addLineHighLight(plugin, cbMeta.pre, cbMeta); 381 | }); 382 | } 383 | //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibWFpbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiaW1wb3J0IHsgbGlua1N5bmMgfSBmcm9tICdmcyc7XG5pbXBvcnQgeyBBcHAsIEVkaXRvciwgTWFya2Rvd25WaWV3LCBNb2RhbCwgTm90aWNlLCBQbHVnaW4sIFBsdWdpblNldHRpbmdUYWIsIFNldHRpbmcsIE1hcmtkb3duUG9zdFByb2Nlc3NvckNvbnRleHQsIE1lbnUsIFNldHRpbmdUYWIsIFRBYnN0cmFjdEZpbGUsIFRGaWxlLCBTZWN0aW9uQ2FjaGUsIFZhdWx0IH0gZnJvbSAnb2JzaWRpYW4nO1xuaW1wb3J0IHsganNvbiB9IGZyb20gJ3N0cmVhbS9jb25zdW1lcnMnO1xuXG5jb25zdCBERUZBVUxUX0xBTkdfQVRUUiA9ICdsYW5ndWFnZS10ZXh0J1xuY29uc3QgREVGQVVMVF9MQU5HID0gJydcbmNvbnN0IExBTkdfUkVHID0gL15sYW5ndWFnZS0vXG5jb25zdCBMSU5FX1NQTElUX01BUksgPSAnXFxuJ1xuXG5jb25zdCB0aXRsZVJlZ0V4cCA9IC9USTpcIihbXlwiXSopXCIvaVxuY29uc3QgaGlnaExpZ2h0TGluZXNSZWdFeHAgPSAvSEw6XCIoW15cIl0qKVwiL2lcbmNvbnN0IGZvbGRSZWdFeHAgPSAvXCJGT0xEXCIvaVxuXG5jb25zdCBDQl9QQURESU5HX1RPUCA9IFwiMzVweFwiIC8vIFx1NEVFM1x1NzgwMVx1NTc1N1x1NEUwQVx1OEZCOVx1OERERFxuXG5pbnRlcmZhY2UgU2V0dGluZ3Mge1xuXHRzdWJzdGl0dXRpb25Ub2tlbkZvclNwYWNlOiBzdHJpbmc7XG5cdHRpdGxlQmFja2dyb3VuZENvbG9yOiBzdHJpbmc7XG5cdHRpdGxlRm9udENvbG9yOiBzdHJpbmc7XG5cdGhpZ2hMaWdodENvbG9yOiBzdHJpbmc7XG5cblx0ZXhjbHVkZUxhbmdzOiBzdHJpbmdbXTsgLy8gXHU5NzAwXHU4OTgxXHU2MzkyXHU5NjY0XHU3Njg0XHU4QkVEXHU4QTAwXG5cblx0c2hvd0xpbmVOdW1iZXI6IGJvb2xlYW47IC8vIFx1NjYzRVx1NzkzQVx1ODg0Q1x1NTNGN1xuXHRzaG93RGl2aWRpbmdMaW5lOiBib29sZWFuO1xuXHRzaG93TGFuZ05hbWVJblRvcFJpZ2h0OiBib29sZWFuO1xufVxuXG5jb25zdCBERUZBVUxUX1NFVFRJTkdTOiBTZXR0aW5ncyA9IHtcblx0c3Vic3RpdHV0aW9uVG9rZW5Gb3JTcGFjZTogdW5kZWZpbmVkLFxuXHR0aXRsZUJhY2tncm91bmRDb2xvcjogXCIjMDAwMDAwMjBcIixcblx0dGl0bGVGb250Q29sb3I6IHVuZGVmaW5lZCxcblx0aGlnaExpZ2h0Q29sb3I6IFwiIzJkODJjYzIwXCIsXG5cblx0ZXhjbHVkZUxhbmdzOiBbXSxcblxuXHRzaG93TGluZU51bWJlcjogdHJ1ZSxcblx0c2hvd0RpdmlkaW5nTGluZTogZmFsc2UsXG5cdHNob3dMYW5nTmFtZUluVG9wUmlnaHQ6IHRydWVcbn07XG5cbmludGVyZmFjZSBDb2RlQmxvY2tNZXRhIHtcblx0Ly8gTGFuZ3VhZ2UgbmFtZVxuXHRsYW5nTmFtZTogc3RyaW5nO1xuXG5cdC8vIENvZGUgYmxvY2sgdG90YWwgbGluZSBzaXplXG5cdGxpbmVTaXplOiBudW1iZXI7XG5cblx0Ly8gQ29kZSBibG9jayAncHJlJyBIVE1MRWxlbWVudFxuXHRwcmU6IEhUTUxFbGVtZW50O1xuXG5cdC8vIENvZGUgYmxvY2sgJ2NvZGUnIEhUTUxFbGVtZW50XG5cdGNvZGU6IEhUTUxFbGVtZW50O1xuXG5cdHRpdGxlOiBzdHJpbmc7IC8vIFx1NEVFM1x1NzgwMVx1NTc1N1x1NjgwN1x1OTg5OFxuXHRpc0NvbGxhcHNlOmJvb2xlYW47IC8vIFx1NjYyRlx1NTQyNlx1OUVEOFx1OEJBNFx1NjI5OFx1NTNFMFxuXG5cdC8vIENvZGUgYmxvY2sgd3JhcCBkaXZcblx0ZGl2OiBIVE1MRWxlbWVudDtcblx0Y29udGVudExpc3Q6IHN0cmluZ1tdO1xuXHRoaWdoTGlnaHRMaW5lczogbnVtYmVyW107XG59XG5cbi8vIFJlZmVyIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2phL2RvY3MvV2ViL0phdmFTY3JpcHQvR3VpZGUvUmVndWxhcl9FeHByZXNzaW9ucyNlc2NhcGluZ1xuZnVuY3Rpb24gZXNjYXBlUmVnRXhwKHN0cjogc3RyaW5nKTogc3RyaW5nIHtcblx0cmV0dXJuIHN0ci5yZXBsYWNlKC9bLiorP149IToke30oKXxbXFxdXFwvXFxcXF0vZywgXCJcXFxcJCZcIik7IC8vIFx1NEUzQVx1NzI3OVx1NkI4QVx1N0IyNlx1NTNGN1x1NTJBMFx1NEUwQVx1OEY2Q1x1NEU0OVx1N0IyNlx1NTNGN1wiXFxcIlxufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBCZXR0ZXJDb2RlQmxvY2sgZXh0ZW5kcyBQbHVnaW4ge1xuXHRzZXR0aW5nczogU2V0dGluZ3M7XG5cblx0YXN5bmMgb25sb2FkKCkge1xuXHRcdGNvbnNvbGUubG9nKFwiTG9hZGluZyBCZXR0ZXIgQ29kZSBCbG9jayBQbHVnaW5cIik7XG5cdFx0YXdhaXQgdGhpcy5sb2FkU2V0dGluZ3MoKTtcblx0XHR0aGlzLmFkZFNldHRpbmdUYWIobmV3IEJldHRlckNvZGVCbG9ja1RhYih0aGlzLmFwcCwgdGhpcykpO1xuXHRcdHRoaXMucmVnaXN0ZXJNYXJrZG93blBvc3RQcm9jZXNzb3IoKGVsLCBjdHgpID0+IHtcblx0XHRcdEJldHRlckNvZGVCbG9ja3MoZWwsIGN0eCwgdGhpcylcblx0XHRcdGFwcC53b3Jrc3BhY2Uub24oJ3Jlc2l6ZScsICgpID0+IHtcblx0XHRcdFx0cmVzaXplTnVtV3JhcEFuZEhMV3JhcChlbCwgY3R4KVxuXHRcdFx0fSlcblx0XHR9KVxuXG5cdH1cblxuXHRvbnVubG9hZCAoKSB7XG5cdFx0Y29uc29sZS5sb2coJ1VubG9hZGluZyBCZXR0ZXIgQ29kZSBCbG9jayBQbHVnaW4nKTtcblx0fVxuXHRcblx0YXN5bmMgbG9hZFNldHRpbmdzKCkge1xuXHRcdHRoaXMuc2V0dGluZ3MgPSBPYmplY3QuYXNzaWduKHt9LCBERUZBVUxUX1NFVFRJTkdTLCBhd2FpdCB0aGlzLmxvYWREYXRhKCkpO1xuXHR9XG5cdFxuXHRhc3luYyBzYXZlU2V0dGluZ3MoKSB7XG5cdFx0YXdhaXQgdGhpcy5zYXZlRGF0YSh0aGlzLnNldHRpbmdzKTtcblx0fVxufVxuXG5jbGFzcyBCZXR0ZXJDb2RlQmxvY2tUYWIgZXh0ZW5kcyBQbHVnaW5TZXR0aW5nVGFiIHtcblx0cGx1Z2luOiBCZXR0ZXJDb2RlQmxvY2s7XG4gIFxuXHRjb25zdHJ1Y3RvcihhcHA6IEFwcCwgcGx1Z2luOiBCZXR0ZXJDb2RlQmxvY2spIHtcblx0ICBzdXBlcihhcHAsIHBsdWdpbik7XG5cdCAgdGhpcy5wbHVnaW4gPSBwbHVnaW47XG5cdH1cbiAgXG5cdGRpc3BsYXkoKTogdm9pZCB7XG5cdCAgbGV0IHsgY29udGFpbmVyRWwgfSA9IHRoaXM7XG4gIFxuXHQgIGNvbnRhaW5lckVsLmVtcHR5KCk7XG5cdFxuXHQgIG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdC5zZXROYW1lKFwiRXhjbHVkZSBsYW5ndWFnZSBsaXN0XCIpXG5cdFx0LnNldERlc2MoXCJUaXRsZSBhbmQgbGluZSBudW1iZXJzIGRvIG5vdCBhcHBseSBpbiB0aGVzZSBsYW5ndWFnZXMsIHNlcGFyYXRlIGJ5IGAsYFwiKVxuXHRcdC5hZGRUZXh0KHRleHQgPT4gdGV4dC5zZXRQbGFjZWhvbGRlcignbGlrZSB0b2RvaXN0LG90aGVyLC4uLicpXG5cdFx0LnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLmV4Y2x1ZGVMYW5ncy5qb2luKCcsJykpXG5cdFx0Lm9uQ2hhbmdlKGFzeW5jICh2YWx1ZSkgPT4ge1xuXHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuZXhjbHVkZUxhbmdzID0gdmFsdWUuc3BsaXQoJywnKTtcblx0XHRcdGF3YWl0IHRoaXMucGx1Z2luLnNhdmVTZXR0aW5ncygpO1xuXHRcdH0pXG5cdFx0KVxuICBcblx0ICBuZXcgU2V0dGluZyhjb250YWluZXJFbCkuc2V0TmFtZShcIkZvbnQgY29sb3Igb2YgdGl0bGVcIikuYWRkVGV4dCgodGMpID0+XG5cdFx0dGNcblx0XHQgIC5zZXRQbGFjZWhvbGRlcihcIkVudGVyIGEgY29sb3JcIilcblx0XHQgIC5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy50aXRsZUZvbnRDb2xvcilcblx0XHQgIC5vbkNoYW5nZShhc3luYyAodmFsdWUpID0+IHtcblx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLnRpdGxlRm9udENvbG9yID0gdmFsdWU7XG5cdFx0XHRhd2FpdCB0aGlzLnBsdWdpbi5zYXZlU2V0dGluZ3MoKTtcblx0XHQgIH0pXG5cdCAgKTtcbiAgXG5cdCAgbmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0LnNldE5hbWUoXCJCYWNrZ3JvdW5kIGNvbG9yIG9mIHRpdGxlXCIpXG5cdFx0LmFkZFRleHQoKHRjKSA9PlxuXHRcdCAgdGNcblx0XHRcdC5zZXRQbGFjZWhvbGRlcihcIiMwMDAwMDAyMFwiKVxuXHRcdFx0LnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLnRpdGxlQmFja2dyb3VuZENvbG9yKVxuXHRcdFx0Lm9uQ2hhbmdlKGFzeW5jICh2YWx1ZSkgPT4ge1xuXHRcdFx0ICB0aGlzLnBsdWdpbi5zZXR0aW5ncy50aXRsZUJhY2tncm91bmRDb2xvciA9IHZhbHVlO1xuXHRcdFx0ICBhd2FpdCB0aGlzLnBsdWdpbi5zYXZlU2V0dGluZ3MoKTtcblx0XHRcdH0pXG5cdFx0KTtcblxuXHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdC5zZXROYW1lKFwiSGlnaExpZ2h0IENvbG9yXCIpXG5cdFx0LmFkZFRleHQoKHRjKSA9PlxuXHRcdCAgdGNcblx0XHRcdC5zZXRQbGFjZWhvbGRlcihcIiMyZDgyY2MyMFwiKVxuXHRcdFx0LnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLmhpZ2hMaWdodENvbG9yKVxuXHRcdFx0Lm9uQ2hhbmdlKGFzeW5jICh2YWx1ZSkgPT4ge1xuXHRcdFx0ICB0aGlzLnBsdWdpbi5zZXR0aW5ncy5oaWdoTGlnaHRDb2xvciA9IHZhbHVlO1xuXHRcdFx0ICBhd2FpdCB0aGlzLnBsdWdpbi5zYXZlU2V0dGluZ3MoKTtcblx0XHRcdH0pXG5cdFx0KTtcblxuXHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdC5zZXROYW1lKFwiU2hvdyBsaW5lIG51bWJlclwiKVxuXHRcdC5hZGRUb2dnbGUoKHRjKSA9PiBcblx0XHR0Yy5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy5zaG93TGluZU51bWJlcilcblx0XHQub25DaGFuZ2UoYXN5bmModmFsdWUpID0+IHtcblx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLnNob3dMaW5lTnVtYmVyID0gdmFsdWU7XG5cdFx0XHRhd2FpdCB0aGlzLnBsdWdpbi5zYXZlU2V0dGluZ3MoKTtcblx0XHR9KVxuXHRcdClcblxuXHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdC5zZXROYW1lKFwiU2hvdyBkaXZpZGluZyBsaW5lXCIpXG5cdFx0LmFkZFRvZ2dsZSgodGMpID0+XG5cdFx0dGMuc2V0VmFsdWUodGhpcy5wbHVnaW4uc2V0dGluZ3Muc2hvd0RpdmlkaW5nTGluZSlcblx0XHQub25DaGFuZ2UoYXN5bmModmFsdWUpID0+IHtcblx0XHRcdHRoaXMucGx1Z2luLnNldHRpbmdzLnNob3dEaXZpZGluZ0xpbmUgPSB2YWx1ZTtcblx0XHRcdGF3YWl0IHRoaXMucGx1Z2luLnNhdmVTZXR0aW5ncygpO1xuXHRcdH0pXG5cdFx0KVxuXG5cdFx0bmV3IFNldHRpbmcoY29udGFpbmVyRWwpXG5cdFx0LnNldE5hbWUoXCJTaG93IGxhbmd1YWdlIG5hbWUgaW4gdGhlIHRvcCByaWdodFwiKVxuXHRcdC5hZGRUb2dnbGUoKHRjKSA9PlxuXHRcdHRjLnNldFZhbHVlKHRoaXMucGx1Z2luLnNldHRpbmdzLnNob3dMYW5nTmFtZUluVG9wUmlnaHQpXG5cdFx0Lm9uQ2hhbmdlKGFzeW5jKHZhbHVlKSA9PiB7XG5cdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5zaG93TGFuZ05hbWVJblRvcFJpZ2h0ID0gdmFsdWU7XG5cdFx0XHRhd2FpdCB0aGlzLnBsdWdpbi5zYXZlU2V0dGluZ3MoKTtcblx0XHR9KVxuXHRcdClcblx0fVxuICB9XG5cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIEJldHRlckNvZGVCbG9ja3MoZWw6IEhUTUxFbGVtZW50LCBjb250ZXh0OiBNYXJrZG93blBvc3RQcm9jZXNzb3JDb250ZXh0LCBwbHVnaW46IEJldHRlckNvZGVCbG9jaykge1xuXHRjb25zdCBzZXR0aW5ncyA9IHBsdWdpbi5zZXR0aW5nc1xuXHRjb25zdCBjb2RlRWxtOiBIVE1MRWxlbWVudCA9IGVsLnF1ZXJ5U2VsZWN0b3IoJ3ByZSA+IGNvZGUnKVxuXHQvLyBvbmx5IGNoYW5nZSBwcmU+Y29kZVxuXHRpZiAoIWNvZGVFbG0pIHsgcmV0dXJuIH1cblxuXHRsZXQgbGFuZyA9IERFRkFVTFRfTEFOR1xuXHQvLyByZXR1cm4gd2hlbiBsYW5nIGlzIGluIGV4Y2x1ZGUgbGlzdFxuXHRpZiAocGx1Z2luLnNldHRpbmdzLmV4Y2x1ZGVMYW5ncy5zb21lKGVMYW5nTmFtZSA9PiBjb2RlRWxtLmNsYXNzTGlzdC5jb250YWlucyhgbGFuZ3VhZ2UtJHtlTGFuZ05hbWV9YCkpKSB7XG5cdCAgcmV0dXJuXG5cdH1cblx0XG5cdGNvZGVFbG0uY2xhc3NMaXN0LmZvckVhY2goKHZhbHVlLCBrZXksIHBhcmVudCkgPT4ge1xuXHQgIGlmIChMQU5HX1JFRy50ZXN0KHZhbHVlKSkge1xuXHRcdGxhbmcgPSB2YWx1ZS5yZXBsYWNlKCdsYW5ndWFnZS0nLCAnJylcblx0XHRyZXR1cm5cblx0ICB9XG5cdH0pXG5cblx0Ly8gaWYgdGhlIGNvZGUgYmxvY2sgaXMgbm90IGRlc2NyaWJlZCwgcmV0dXJuXG5cdGlmKGxhbmcgPT0gREVGQVVMVF9MQU5HKSB7XG5cdFx0cmV0dXJuXG5cdH1cblxuXHRsZXQgY29kZUJsb2NrID0gY29udGV4dC5nZXRTZWN0aW9uSW5mbyhjb2RlRWxtKVxuXHRsZXQgY29kZUJsb2NrRmlyc3RMaW5lID0gXCJcIlxuXG5cdGlmKGNvZGVCbG9jaykge1xuXHRcdGxldCB2aWV3ID0gYXBwLndvcmtzcGFjZS5nZXRBY3RpdmVWaWV3T2ZUeXBlKE1hcmtkb3duVmlldylcblx0XHRjb2RlQmxvY2tGaXJzdExpbmUgPSB2aWV3LmVkaXRvci5nZXRMaW5lKGNvZGVCbG9jay5saW5lU3RhcnQpXG5cdH0gZWxzZSB7IFxuXHRcdGxldCBmaWxlID0gYXBwLnZhdWx0LmdldEFic3RyYWN0RmlsZUJ5UGF0aChjb250ZXh0LnNvdXJjZVBhdGgpXG5cdFx0bGV0IGNhY2hlID0gYXBwLm1ldGFkYXRhQ2FjaGUuZ2V0Q2FjaGUoY29udGV4dC5zb3VyY2VQYXRoKVxuXHRcdGxldCBmaWxlQ29udGVudCA9IGF3YWl0IGFwcC52YXVsdC5jYWNoZWRSZWFkKDxURmlsZT4gZmlsZSlcblx0XHRsZXQgZmlsZUNvbnRlbnRMaW5lcyA9IGZpbGVDb250ZW50LnNwbGl0KC9cXG4vZylcblxuXHRcdGxldCBjb2RlQmxvY2tGaXJzdExpbmVzOiBzdHJpbmdbXSA9IFtdXG5cdFx0bGV0IGNvZGVCbG9ja1NlY3Rpb25zOiBTZWN0aW9uQ2FjaGVbXSA9IFtdXG5cblx0XHRjYWNoZS5zZWN0aW9ucz8uZm9yRWFjaChhc3luYyBlbGVtZW50ID0+IHtcblx0XHRcdGlmKGVsZW1lbnQudHlwZSA9PSBcImNvZGVcIikge1xuXHRcdFx0XHRsZXQgbGluZVN0YXJ0ID0gZWxlbWVudC5wb3NpdGlvbi5zdGFydC5saW5lXG5cdFx0XHRcdGNvZGVCbG9ja0ZpcnN0TGluZSA9IGZpbGVDb250ZW50TGluZXNbbGluZVN0YXJ0XVxuXHRcdFx0XHRjb2RlQmxvY2tTZWN0aW9ucy5wdXNoKGVsZW1lbnQpXG5cdFx0XHRcdGNvZGVCbG9ja0ZpcnN0TGluZXMucHVzaChjb2RlQmxvY2tGaXJzdExpbmUpXG5cdFx0XHR9XG5cdFx0fSk7XG5cdFx0ZXhwb3J0UERGKGVsLCBwbHVnaW4sIGNvZGVCbG9ja0ZpcnN0TGluZXMsIGNvZGVCbG9ja1NlY3Rpb25zKVxuXHRcdHJldHVyblxuXHR9XG5cblx0bGV0IHRpdGxlOiBzdHJpbmcgPSBcIlwiXG5cdGxldCBoaWdoTGlnaHRMaW5lczogbnVtYmVyW10gPSBbXVxuXHRpZihjb2RlQmxvY2tGaXJzdExpbmUubWF0Y2godGl0bGVSZWdFeHApICE9IG51bGwpIHtcblx0XHR0aXRsZSA9IGNvZGVCbG9ja0ZpcnN0TGluZS5tYXRjaCh0aXRsZVJlZ0V4cClbMV1cblx0fVxuXHRpZihjb2RlQmxvY2tGaXJzdExpbmUubWF0Y2goaGlnaExpZ2h0TGluZXNSZWdFeHApICE9IG51bGwpIHtcblx0XHRsZXQgaGlnaExpZ2h0TGluZXNJbmZvID0gY29kZUJsb2NrRmlyc3RMaW5lLm1hdGNoKGhpZ2hMaWdodExpbmVzUmVnRXhwKVsxXVxuXHRcdGhpZ2hMaWdodExpbmVzID0gYW5hbHlzZUhpZ2hMaWdodExpbmVzKGhpZ2hMaWdodExpbmVzSW5mbylcblx0fVxuXG5cdGxldCBpc0NvbGxhcHNlID0gZmFsc2U7XG5cdGlmKGZvbGRSZWdFeHAudGVzdChjb2RlQmxvY2tGaXJzdExpbmUpKSB7XG5cdFx0aXNDb2xsYXBzZSA9IHRydWVcblx0fVxuXG5cdGNvbnN0IHByZSA9IGNvZGVFbG0ucGFyZW50RWxlbWVudCAvLyBjb2RlLWJsb2NrLXByZV9faGFzLWxpbmVudW1cblx0Y29uc3QgZGl2ID0gcHJlLnBhcmVudEVsZW1lbnQgLy8gY2xhc3MgY29kZS1ibG9jay13cmFwXG5cblx0LyogY29uc3QgeyBsaW5lU3RhcnQsIGxpbmVFbmQgfSA9IGN0eC5nZXRTZWN0aW9uSW5mbyhlbClcblx0Y29uc3QgbGluZVNpemUgPSBsaW5lRW5kIC0gbGluZVN0YXJ0IC0gMSAqL1xuXHRjb25zdCBjb250ZW50TGlzdDogc3RyaW5nW10gPSBjb2RlRWxtLnRleHRDb250ZW50LnNwbGl0KExJTkVfU1BMSVRfTUFSSylcblx0Ly8gY29uc3QgbGluZVNpemUgPSBjb250ZW50TGlzdC5sZW5ndGggLSAxXG5cdGNvbnN0IGxpbmVTaXplID0gY29kZUJsb2NrLmxpbmVFbmQgLSBjb2RlQmxvY2subGluZVN0YXJ0IC0gMVxuXG5cdGNvbnN0IGNiTWV0YSA9IHsgbGFuZ05hbWU6IGxhbmcsIGxpbmVTaXplLCBwcmUsIGNvZGU6IGNvZGVFbG0sIHRpdGxlLCBpc0NvbGxhcHNlLCBkaXYsIGNvbnRlbnRMaXN0LCBoaWdoTGlnaHRMaW5lc31cblxuXHRjb25zdCB7c2hvd0xpbmVOdW1iZXJ9ID0gcGx1Z2luLnNldHRpbmdzXG5cblx0YWRkQ29kZVRpdGxlV3JhcHBlcihwbHVnaW4sIHByZSwgY2JNZXRhKVxuXHQvL2FkZEljb25Ub1RpdGxlKHBsdWdpbiwgcHJlLCBjYk1ldGEpXG5cdGFkZENvZGVUaXRsZShwbHVnaW4sIHByZSwgY2JNZXRhKTtcblxuXHQvLyBhZGQgbGluZSBudW1iZXJcblx0aWYgKHNob3dMaW5lTnVtYmVyKSB7XG5cdFx0YWRkTGluZU51bWJlcihwbHVnaW4sIGNiTWV0YSlcblx0fVxuXG5cdGFkZExpbmVIaWdoTGlnaHQocGx1Z2luLCBwcmUsIGNiTWV0YSlcblxuXHRyZXNpemVOdW1XcmFwQW5kSExXcmFwKGVsLGNvbnRleHQpIC8vIFx1OEMwM1x1NzUyOFx1NEUwMFx1NkIyMVx1NEVFNVx1ODlFM1x1NTFCM1x1NjdEMFx1NEU5Qlx1NjVGNlx1NTAxOVx1NjI1M1x1NUYwMFx1NjU4N1x1NEVGNlx1ODg0Q1x1OUFEOFx1NjcyQVx1ODhBQlx1OTFDRFx1OEJCRVx1OUFEOFx1NUVBNlxufVxuXG5mdW5jdGlvbiBjcmVhdGVFbGVtZW50ICh0YWdOYW1lOiBzdHJpbmcsIGRlZmF1bHRDbGFzc05hbWU/OiBzdHJpbmcpIHtcblx0Y29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodGFnTmFtZSlcblx0aWYgKGRlZmF1bHRDbGFzc05hbWUpIHtcblx0ICBlbGVtZW50LmNsYXNzTmFtZSA9IGRlZmF1bHRDbGFzc05hbWVcblx0fVxuXHRyZXR1cm4gZWxlbWVudFxufVxuXG5mdW5jdGlvbiBhZGRDb2RlVGl0bGVXcmFwcGVyKHBsdWdpbjogQmV0dGVyQ29kZUJsb2NrLCBwcmVFbG06IEhUTUxFbGVtZW50LCBjYk1ldGE6IENvZGVCbG9ja01ldGEpIHtcblx0cHJlRWxtLnN0eWxlLnNldFByb3BlcnR5KFwicG9zaXRpb25cIiwgXCJyZWxhdGl2ZVwiLCBcImltcG9ydGFudFwiKTtcblx0cHJlRWxtLnN0eWxlLnNldFByb3BlcnR5KFwicGFkZGluZy10b3BcIiwgQ0JfUEFERElOR19UT1AsIFwiaW1wb3J0YW50XCIpO1xuXG5cdGxldCB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInByZVwiKVxuXHRpZihjYk1ldGEuaXNDb2xsYXBzZSkge1xuXHRcdHdyYXBwZXIuc2V0QXR0cmlidXRlKFwiY2xvc2VkXCIsXCJcIilcblx0fVxuXHR3cmFwcGVyLmNsYXNzTmFtZSA9IFwib2JzaWRpYW4tZW1iZWRkZWQtY29kZS10aXRsZV9fY29kZS1ibG9jay10aXRsZVwiXG5cblx0d3JhcHBlci5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSBwbHVnaW4uc2V0dGluZ3MudGl0bGVCYWNrZ3JvdW5kQ29sb3IgfHwgXCIjMDAwMDAwMjBcIjtcblxuXHRsZXQgY29sbGFwc2VyID0gY3JlYXRlRWxlbWVudChcImRpdlwiLFwiY29sbGFwc2VyXCIpXG5cdGxldCBoYW5kbGUgPSBjcmVhdGVFbGVtZW50KFwiZGl2XCIsIFwiaGFuZGxlXCIpXG5cdGNvbGxhcHNlci5hcHBlbmRDaGlsZChoYW5kbGUpXG5cdHdyYXBwZXIuYXBwZW5kQ2hpbGQoY29sbGFwc2VyKVxuXG5cdHdyYXBwZXIuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLGZ1bmN0aW9uKHRoaXM6IGFueSkge1xuXHRcdGlmKHdyYXBwZXIuaGFzQXR0cmlidXRlKFwiY2xvc2VkXCIpKXtcblx0XHRcdHdyYXBwZXIucmVtb3ZlQXR0cmlidXRlKFwiY2xvc2VkXCIpXG5cdFx0fSBlbHNlIHtcblx0XHRcdHdyYXBwZXIuc2V0QXR0cmlidXRlKFwiY2xvc2VkXCIsJycpXG5cdFx0fVxuXHR9KVxuXG5cdHByZUVsbS5hcHBlbmRDaGlsZCh3cmFwcGVyKVxufVxuXG5mdW5jdGlvbiBhZGRDb2RlVGl0bGUgKHBsdWdpbjogQmV0dGVyQ29kZUJsb2NrLCBwcmVFbG06IEhUTUxFbGVtZW50LCBjYk1ldGE6IENvZGVCbG9ja01ldGEpIHtcblx0bGV0IHdyYXBwZXIgPSBwcmVFbG0ucXVlcnlTZWxlY3RvcihcIi5vYnNpZGlhbi1lbWJlZGRlZC1jb2RlLXRpdGxlX19jb2RlLWJsb2NrLXRpdGxlXCIpXG5cblx0bGV0IHRpdGxlRWxtID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKVxuXHR0aXRsZUVsbS5jbGFzc05hbWUgPSBcInRpdGxlXCJcblxuXHR0aXRsZUVsbS5hcHBlbmRUZXh0KGNiTWV0YS50aXRsZSlcblx0d3JhcHBlci5hcHBlbmRDaGlsZCh0aXRsZUVsbSlcblxuXHRpZihwbHVnaW4uc2V0dGluZ3MudGl0bGVGb250Q29sb3IpIHtcblx0XHR0aXRsZUVsbS5zdHlsZS5zZXRQcm9wZXJ0eShcImNvbG9yXCIsIHBsdWdpbi5zZXR0aW5ncy50aXRsZUZvbnRDb2xvciwgXCJpbXBvcnRhbnRcIilcblx0fVxuXHRcblx0aWYocGx1Z2luLnNldHRpbmdzLnNob3dMYW5nTmFtZUluVG9wUmlnaHQpIHtcblx0XHRsZXQgbGFuZ05hbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiZGl2XCIpOyAvLyBcdTU3MjhcdTUzRjNcdTRGQTdcdTZERkJcdTUyQTBcdTRFRTNcdTc4MDFcdTdDN0JcdTU3OEJcblx0XHRsZXQgbGFuZ05hbWVTdHJpbmcgPSBjYk1ldGEubGFuZ05hbWVcblx0XHRsYW5nTmFtZVN0cmluZyA9IGxhbmdOYW1lU3RyaW5nWzBdLnRvVXBwZXJDYXNlKCkgKyBsYW5nTmFtZVN0cmluZy5zbGljZSgxKSAvLyBcdTk5OTZcdTVCNTdcdTZCQ0RcdTU5MjdcdTUxOTlcblx0XHRsYW5nTmFtZS5hcHBlbmRUZXh0KGxhbmdOYW1lU3RyaW5nKTtcblx0XHRsYW5nTmFtZS5jbGFzc05hbWUgPSBcImxhbmdOYW1lXCI7XG5cdFx0d3JhcHBlci5hcHBlbmRDaGlsZChsYW5nTmFtZSk7XG5cdH1cblxuXHRwcmVFbG0ucHJlcGVuZCh3cmFwcGVyKTtcblxufVxuXG5mdW5jdGlvbiBhZGRMaW5lTnVtYmVyIChwbHVnaW46IEJldHRlckNvZGVCbG9jaywgY2JNZXRhOiBDb2RlQmxvY2tNZXRhKSB7XG5cdGNvbnN0IHsgbGluZVNpemUsIHByZSwgZGl2IH0gPSBjYk1ldGFcblx0Ly8gbGV0IGRpdiBwb3NpdGlvbjogcmVsYXRpdmU7XG5cdGRpdi5jbGFzc0xpc3QuYWRkKCdjb2RlLWJsb2NrLXdyYXAnKVxuXG5cdC8vIGNvbnN0IHsgZm9udFNpemUsIGxpbmVIZWlnaHQgfSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGNiTWV0YS5jb2RlKVxuXHRjb25zdCBsaW5lTnVtYmVyID0gY3JlYXRlRWxlbWVudCgnc3BhbicsICdjb2RlLWJsb2NrLWxpbmVudW0td3JhcCcpXG5cdGxpbmVOdW1iZXIuc3R5bGUudG9wID0gQ0JfUEFERElOR19UT1A7XG5cdEFycmF5LmZyb20oeyBsZW5ndGg6IGxpbmVTaXplIH0sICh2LCBrKSA9PiBrKS5mb3JFYWNoKGkgPT4ge1xuXHQgIGNvbnN0IHNpbmdsZUxpbmUgPSBjcmVhdGVFbGVtZW50KCdzcGFuJywgJ2NvZGUtYmxvY2stbGluZW51bScpXG5cdCAgLy8gc2luZ2xlTGluZS5zdHlsZS5mb250U2l6ZSA9IGZvbnRTaXplXG5cdCAgLy8gc2luZ2xlTGluZS5zdHlsZS5saW5lSGVpZ2h0ID0gbGluZUhlaWdodFxuXHQgIGxpbmVOdW1iZXIuYXBwZW5kQ2hpbGQoc2luZ2xlTGluZSlcblx0fSlcblx0XG5cdGlmKHBsdWdpbi5zZXR0aW5ncy5zaG93RGl2aWRpbmdMaW5lKSB7XG5cdFx0bGluZU51bWJlci5zdHlsZS5ib3JkZXJSaWdodCA9IFwiMXB4IGN1cnJlbnRDb2xvciBzb2xpZFwiXG5cdH1cblxuXHRwcmUuYXBwZW5kQ2hpbGQobGluZU51bWJlcilcblx0cHJlLmNsYXNzTGlzdC5hZGQoJ2NvZGUtYmxvY2stcHJlX19oYXMtbGluZW51bScpXG59XG5cbmZ1bmN0aW9uIGFkZExpbmVIaWdoTGlnaHQocGx1Z2luOiBCZXR0ZXJDb2RlQmxvY2ssIHByZUVsbTogSFRNTEVsZW1lbnQsIGNiTWV0YTogQ29kZUJsb2NrTWV0YSkge1xuXHRpZihjYk1ldGEuaGlnaExpZ2h0TGluZXMubGVuZ3RoID09IDApIHJldHVyblxuXG5cdGxldCBoaWdoTGlnaHRXcmFwID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInByZVwiKVxuXHRoaWdoTGlnaHRXcmFwLmNsYXNzTmFtZSA9IFwiY29kZS1ibG9jay1oaWdobGlnaHQtd3JhcFwiXG5cdGZvcihsZXQgaSA9IDA7IGkgPCBjYk1ldGEubGluZVNpemU7IGkrKykge1xuXHRcdGNvbnN0IHNpbmdsZUxpbmUgPSBjcmVhdGVFbGVtZW50KFwic3BhblwiLCAnY29kZS1ibG9jay1oaWdobGlnaHQnKVxuXHRcdGlmKGNiTWV0YS5oaWdoTGlnaHRMaW5lcy5jb250YWlucyhpKzEpKSB7XG5cdFx0XHRzaW5nbGVMaW5lLnN0eWxlLmJhY2tncm91bmRDb2xvciA9IHBsdWdpbi5zZXR0aW5ncy5oaWdoTGlnaHRDb2xvciB8fCBcIiMyZDgyY2MyMFwiXG5cdFx0fVxuXHRcdGhpZ2hMaWdodFdyYXAuYXBwZW5kQ2hpbGQoc2luZ2xlTGluZSlcblx0fVxuXG5cdHByZUVsbS5hcHBlbmRDaGlsZChoaWdoTGlnaHRXcmFwKVxufVxuXG5mdW5jdGlvbiBhbmFseXNlSGlnaExpZ2h0TGluZXMoc3RyOiBzdHJpbmcpOiBudW1iZXJbXSB7XG5cdHN0ciA9IHN0ci5yZXBsYWNlKC9cXHMqL2csIFwiXCIpIC8vIFx1NTNCQlx1OTY2NFx1NUI1N1x1N0IyNlx1NEUzMlx1NEUyRFx1NjI0MFx1NjcwOVx1N0E3QVx1NjgzQ1xuXHRjb25zdCByZXN1bHQ6IG51bWJlcltdID0gW11cblxuXHRsZXQgc3RycyA9IHN0ci5zcGxpdChcIixcIilcblx0c3Rycy5mb3JFYWNoKGl0ID0+IHtcblx0XHRpZigvXFx3Ky1cXHcrLy50ZXN0KGl0KSkgeyAvLyBcdTU5ODJcdTY3OUNcdTUzMzlcdTkxNEQgMS0zIFx1OEZEOVx1NjgzN1x1NzY4NFx1NjgzQ1x1NUYwRlx1RkYwQ1x1NEY5RFx1NkIyMVx1NkRGQlx1NTJBMFx1NjU3MFx1NUI1N1xuXHRcdFx0bGV0IGxlZnQgPSBOdW1iZXIoaXQuc3BsaXQoJy0nKVswXSlcblx0XHRcdGxldCByaWdodCA9IE51bWJlcihpdC5zcGxpdCgnLScpWzFdKVxuXHRcdFx0Zm9yKGxldCBpID0gbGVmdDsgaSA8PSByaWdodDsgaSsrKSB7XG5cdFx0XHRcdHJlc3VsdC5wdXNoKGkpXG5cdFx0XHR9XG5cdFx0fSBlbHNlIHtcblx0XHRcdHJlc3VsdC5wdXNoKE51bWJlcihpdCkpXG5cdFx0fVxuXHR9KVxuXG5cdHJldHVybiByZXN1bHRcbn1cblxuZnVuY3Rpb24gYWRkSWNvblRvVGl0bGUocGx1Z2luOiBCZXR0ZXJDb2RlQmxvY2ssIHByZUVsbTogSFRNTEVsZW1lbnQsIGNiTWV0YTogQ29kZUJsb2NrTWV0YSkge1xuXHRsZXQgdGl0bGUgPSBwcmVFbG0ucXVlcnlTZWxlY3RvckFsbChcIi5vYnNpZGlhbi1lbWJlZGRlZC1jb2RlLXRpdGxlX19jb2RlLWJsb2NrLXRpdGxlXCIpXG5cblx0dGl0bGUuZm9yRWFjaChpdCA9PiB7XG5cdFx0bGV0IGljb25XcmFwID0gY3JlYXRlRWxlbWVudChcImRpdlwiLFwiaWNvbi13cmFwXCIpXG5cdFx0bGV0IGljb24gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiaW1nXCIpXG5cdFx0aWNvbi5zcmMgPSBcIlwiXG5cdFx0aWNvbldyYXAuYXBwZW5kQ2hpbGQoaWNvbilcblx0XHRpdC5hcHBlbmRDaGlsZChpY29uV3JhcClcblx0fSlcblx0XG59XG5cbi8vIFx1NTcyOFx1ODFFQVx1NTJBOFx1NjM2Mlx1ODg0Q1x1NjVGNlx1NUJGOVx1NjU3MFx1NUI1N1x1NTQ4Q1x1OUFEOFx1NEVBRVx1ODg0Q1x1OTFDRFx1NjVCMFx1OEJCRVx1N0Y2RVx1OUFEOFx1NUVBNlxuLy8gVGhlc2UgY29kZXMgcmVmZXIgdG8gdGhlIGh0dHBzOi8vZ2l0aHViLmNvbS9saWp5emUvb2JzaWRpYW4tYWR2YW5jZWQtY29kZWJsb2NrXG5mdW5jdGlvbiByZXNpemVOdW1XcmFwQW5kSExXcmFwKGVsOiBIVE1MRWxlbWVudCwgY29udGV4dDogTWFya2Rvd25Qb3N0UHJvY2Vzc29yQ29udGV4dCkge1xuXHRzZXRUaW1lb3V0KGFzeW5jIGZ1bmN0aW9uKCl7IC8vIFx1NUVGNlx1NjVGNjEwMFx1NkJFQlx1NzlEMlx1NEVFNVx1ODlFM1x1NTFCM1x1NjdEMFx1NEU5Qlx1NjVGNlx1NTAxOVx1NjI1M1x1NUYwMFx1NjU4N1x1NEVGNlx1ODg0Q1x1OUFEOFx1NjcyQVx1ODhBQlx1OTFDRFx1OEJCRVx1OUFEOFx1NUVBNlxuXHRcdC8vIGNvbnNvbGUubG9nKCdvbiByZXNpemUnKVxuXHRcdGxldCBjb2RlQmxvY2tFbCA6IEhUTUxFbGVtZW50ID0gZWwucXVlcnlTZWxlY3RvcigncHJlID4gY29kZScpXG5cdFx0aWYoIWNvZGVCbG9ja0VsKSByZXR1cm5cblxuXHRcdGxldCBudW1XcmFwID0gZWwucXVlcnlTZWxlY3RvcignLmNvZGUtYmxvY2stbGluZW51bS13cmFwJylcblx0XHRsZXQgaGlnaFdyYXAgPSBlbC5xdWVyeVNlbGVjdG9yKCcuY29kZS1ibG9jay1oaWdobGlnaHQtd3JhcCcpXG5cblx0XHRsZXQgY29kZUJsb2NrSW5mbyA9IGNvbnRleHQuZ2V0U2VjdGlvbkluZm8oY29kZUJsb2NrRWwpXG5cdFx0Ly8gbGV0IHZpZXcgPSBhcHAud29ya3NwYWNlLmdldEFjdGl2ZVZpZXdPZlR5cGUoTWFya2Rvd25WaWV3KVxuXHRcdC8vIGxldCBjb2RlQmxvY2tMaW5lTnVtID0gY29kZUJsb2NrSW5mby5saW5lRW5kIC0gY29kZUJsb2NrSW5mby5saW5lU3RhcnQgLSAxIC8vIFx1OTY2NFx1NTNCQlx1OTk5Nlx1NUMzRVx1NEUyNFx1ODg0Q1xuXHRcdGxldCB2aWV3XG5cdFx0bGV0IGNvZGVCbG9ja0xpbmVOdW1cblxuXHRcdGxldCBsaW5lU3RhcnQgPSAwXG5cdFx0bGV0IGxpbmVFbmQgPSAwXG5cdFx0aWYoY29kZUJsb2NrSW5mbykge1xuXHRcdFx0dmlldyA9IGFwcC53b3Jrc3BhY2UuZ2V0QWN0aXZlVmlld09mVHlwZShNYXJrZG93blZpZXcpXG5cdFx0XHRjb2RlQmxvY2tMaW5lTnVtID0gY29kZUJsb2NrSW5mby5saW5lRW5kIC0gY29kZUJsb2NrSW5mby5saW5lU3RhcnQgLSAxIC8vIFx1OTY2NFx1NTNCQlx1OTk5Nlx1NUMzRVx1NEUyNFx1ODg0Q1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRyZXR1cm5cblx0XHRcdC8vIGxldCBmaWxlID0gYXBwLnZhdWx0LmdldEFic3RyYWN0RmlsZUJ5UGF0aChjb250ZXh0LnNvdXJjZVBhdGgpXG5cdFx0XHQvLyBsZXQgY2FjaGUgPSBhcHAubWV0YWRhdGFDYWNoZS5nZXRDYWNoZShjb250ZXh0LnNvdXJjZVBhdGgpXG5cdFxuXHRcdFx0Ly8gY2FjaGUuc2VjdGlvbnM/LmZvckVhY2goYXN5bmMgZWxlbWVudCA9PiB7XG5cdFx0XHQvLyBcdGlmKGVsZW1lbnQudHlwZSA9PSBcImNvZGVcIikge1xuXHRcdFx0Ly8gXHRcdGxpbmVTdGFydCA9IGVsZW1lbnQucG9zaXRpb24uc3RhcnQubGluZVxuXHRcdFx0Ly8gXHRcdGxpbmVFbmQgPSBlbGVtZW50LnBvc2l0aW9uLmVuZC5saW5lXG5cdFx0XHQvLyBcdFx0Y29kZUJsb2NrTGluZU51bSA9IGxpbmVFbmQgLSBsaW5lU3RhcnQgLSAxXG5cdFx0XHQvLyBcdFx0cmV0dXJuXG5cdFx0XHQvLyBcdH1cblx0XHRcdC8vIH0pO1xuXHRcdFx0Ly8gbGV0IGZpbGUgPSBhcHAudmF1bHQuZ2V0QWJzdHJhY3RGaWxlQnlQYXRoKGNvbnRleHQuc291cmNlUGF0aClcblx0XHRcdC8vIGxldCBjYWNoZSA9IGFwcC5tZXRhZGF0YUNhY2hlLmdldENhY2hlKGNvbnRleHQuc291cmNlUGF0aClcblx0XHRcdC8vIGxldCBmaWxlQ29udGVudCA9IGF3YWl0IGFwcC52YXVsdC5jYWNoZWRSZWFkKDxURmlsZT4gZmlsZSlcblx0XHRcdC8vIGxldCBmaWxlQ29udGVudExpbmVzID0gZmlsZUNvbnRlbnQuc3BsaXQoL1xcbi9nKVxuXHRcdH1cblxuXHRcdGxldCBzcGFuID0gY3JlYXRlRWxlbWVudChcInNwYW5cIilcblxuXHRcdGZvcihsZXQgaSA9IDA7IGkgPCBjb2RlQmxvY2tMaW5lTnVtOyBpKyspIHtcblx0XHRcdGxldCBvbmVMaW5lVGV4dFxuXHRcdFx0aWYodmlldyl7XG5cdFx0XHRcdG9uZUxpbmVUZXh0ID0gdmlldy5lZGl0b3IuZ2V0TGluZShjb2RlQmxvY2tJbmZvLmxpbmVTdGFydCArIGkgKyAxKVxuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0Ly8gb25lTGluZVRleHQgPSBmaWxlQ29udGVudExpbmVzW2xpbmVTdGFydCArIDEgKyBpXVxuXHRcdFx0XHQvLyBsZXQgZmlsZSA9IGFwcC52YXVsdC5nZXRBYnN0cmFjdEZpbGVCeVBhdGgoY29udGV4dC5zb3VyY2VQYXRoKVxuXHRcdFx0XHQvLyBsZXQgY2FjaGUgPSBhcHAubWV0YWRhdGFDYWNoZS5nZXRDYWNoZShjb250ZXh0LnNvdXJjZVBhdGgpXG5cdFx0XHRcdC8vIGxldCBmaWxlQ29udGVudCA9IGF3YWl0IGFwcC52YXVsdC5jYWNoZWRSZWFkKDxURmlsZT4gZmlsZSlcblx0XHRcdFx0Ly8gbGV0IGZpbGVDb250ZW50TGluZXMgPSBmaWxlQ29udGVudC5zcGxpdCgvXFxuL2cpXG5cdFx0XHRcdC8vIG9uZUxpbmVUZXh0ID0gZmlsZUNvbnRlbnRMaW5lc1tjYWNoZS5zZWN0aW9uc11cblx0XHRcdH1cblx0XHRcdHNwYW4uaW5uZXJIVE1MID0gb25lTGluZVRleHQgfHwgXCIwXCJcblxuXHRcdFx0Y29kZUJsb2NrRWwuYXBwZW5kQ2hpbGQoc3Bhbilcblx0XHRcdHNwYW4uc3R5bGUuZGlzcGxheSA9ICdibG9jaydcblxuXHRcdFx0bGV0IGxpbmVIZWlnaHQgPSBzcGFuLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmhlaWdodCArICdweCcgLy8gXHU2RDRCXHU5MUNGXHU2NzJDXHU4ODRDXHU2NTg3XHU1QjU3XHU3Njg0XHU5QUQ4XHU1RUE2XG5cblx0XHRcdC8vIGNvbnNvbGUubG9nKGxpbmVIZWlnaHQgKyAnICAgICcgKyBzcGFuLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLndpZHRoKTtcblx0XHRcdFxuXHRcdFx0bGV0IG51bU9uZUxpbmUgPSBudW1XcmFwPyBudW1XcmFwLmNoaWxkTm9kZXNbaV0gYXMgSFRNTEVsZW1lbnQgOiBudWxsXG5cdFx0XHRsZXQgaGxPbmVMaW5lID0gaGlnaFdyYXA/IGhpZ2hXcmFwLmNoaWxkTm9kZXNbaV0gYXMgSFRNTEVsZW1lbnQgOiBudWxsXG5cblx0XHRcdGlmKG51bU9uZUxpbmUpIG51bU9uZUxpbmUuc3R5bGUuaGVpZ2h0ID0gbGluZUhlaWdodDtcblx0XHRcdGlmKGhsT25lTGluZSkgaGxPbmVMaW5lLnN0eWxlLmhlaWdodCA9IGxpbmVIZWlnaHQ7XG5cblx0XHRcdHNwYW4ucmVtb3ZlKCkgLy8gXHU2RDRCXHU5MUNGXHU1QjhDXHU1NDBFXHU1MjIwXHU2Mzg5XG5cdFx0fVxuXHR9LCAxMDApXG59XG5cbmZ1bmN0aW9uIGV4cG9ydFBERihlbDogSFRNTEVsZW1lbnQsIHBsdWdpbjogQmV0dGVyQ29kZUJsb2NrLCBjb2RlQmxvY2tGaXJzdExpbmVzOiBzdHJpbmdbXSwgY29kZUJsb2NrU2VjdGlvbnM6IFNlY3Rpb25DYWNoZVtdKSB7XG5cdGxldCBjb2RlQmxvY2tzID0gZWwucXVlcnlTZWxlY3RvckFsbCgncHJlID4gY29kZScpXG5cdGNvZGVCbG9ja3MuZm9yRWFjaCgoY29kZUVsbSwga2V5KSA9PiB7XG5cdFx0bGV0IGxhbmdOYW1lID0gXCJcIiwgdGl0bGUgPSBcIlwiLCBoaWdoTGlnaHRMaW5lczogbnVtYmVyW10gPSBbXVxuXHRcdGNvZGVFbG0uY2xhc3NMaXN0LmZvckVhY2godmFsdWUgPT4ge1xuXHRcdFx0aWYoTEFOR19SRUcudGVzdCh2YWx1ZSkpIHtcblx0XHRcdFx0bGFuZ05hbWUgPSB2YWx1ZS5yZXBsYWNlKCdsYW5ndWFnZS0nLCAnJylcblx0XHRcdFx0cmV0dXJuXG5cdFx0XHR9XG5cdFx0fSlcblxuXHRcdGlmKGNvZGVCbG9ja0ZpcnN0TGluZXNba2V5XS5tYXRjaCh0aXRsZVJlZ0V4cCkgIT0gbnVsbCkge1xuXHRcdFx0dGl0bGUgPSBjb2RlQmxvY2tGaXJzdExpbmVzW2tleV0ubWF0Y2godGl0bGVSZWdFeHApWzFdXG5cdFx0fVxuXHRcdGlmKGNvZGVCbG9ja0ZpcnN0TGluZXNba2V5XS5tYXRjaChoaWdoTGlnaHRMaW5lc1JlZ0V4cCkgIT0gbnVsbCkge1xuXHRcdFx0bGV0IGhpZ2hMaWdodExpbmVzSW5mbyA9IGNvZGVCbG9ja0ZpcnN0TGluZXNba2V5XS5tYXRjaChoaWdoTGlnaHRMaW5lc1JlZ0V4cClbMV1cblx0XHRcdGhpZ2hMaWdodExpbmVzID0gYW5hbHlzZUhpZ2hMaWdodExpbmVzKGhpZ2hMaWdodExpbmVzSW5mbylcblx0XHR9XG5cblx0XHRsZXQgbGluZVNpemUgPSBjb2RlQmxvY2tTZWN0aW9uc1trZXldLnBvc2l0aW9uLmVuZC5saW5lIC0gY29kZUJsb2NrU2VjdGlvbnNba2V5XS5wb3NpdGlvbi5zdGFydC5saW5lIC0gMVxuXG5cdFx0bGV0IGNiTWV0YTogQ29kZUJsb2NrTWV0YSA9IHtcblx0XHRcdGxhbmdOYW1lOiBsYW5nTmFtZSxcblx0XHRcdGxpbmVTaXplOiBsaW5lU2l6ZSxcblx0XHRcdHByZTogY29kZUVsbS5wYXJlbnRFbGVtZW50LFxuXHRcdFx0Y29kZTogY29kZUVsbSBhcyBIVE1MRWxlbWVudCxcblx0XHRcdHRpdGxlOiB0aXRsZSxcblx0XHRcdGlzQ29sbGFwc2U6IGZhbHNlLFxuXHRcdFx0ZGl2OiBjb2RlRWxtLnBhcmVudEVsZW1lbnQucGFyZW50RWxlbWVudCxcblx0XHRcdGNvbnRlbnRMaXN0OiBbXSxcblx0XHRcdGhpZ2hMaWdodExpbmVzOiBoaWdoTGlnaHRMaW5lc1xuXHRcdH1cblx0XHRhZGRDb2RlVGl0bGVXcmFwcGVyKHBsdWdpbiwgY29kZUVsbS5wYXJlbnRFbGVtZW50LCBjYk1ldGEpIC8vIFx1NUJGQ1x1NTFGQVx1NTNENlx1NkQ4OFx1NEVFM1x1NzgwMVx1NTc1N1x1NjI5OFx1NTNFMFxuXHRcdGFkZENvZGVUaXRsZShwbHVnaW4sIGNiTWV0YS5wcmUsIGNiTWV0YSlcblx0XHRpZihwbHVnaW4uc2V0dGluZ3Muc2hvd0xpbmVOdW1iZXIpIHtcblx0XHRcdGFkZExpbmVOdW1iZXIocGx1Z2luLCBjYk1ldGEpXG5cdFx0fVxuXHRcdGFkZExpbmVIaWdoTGlnaHQocGx1Z2luLCBjYk1ldGEucHJlLCBjYk1ldGEpXG5cdH0pXG59Il0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUNBLHNCQUF1TDtBQUl2TCxJQUFNLGVBQWU7QUFDckIsSUFBTSxXQUFXO0FBQ2pCLElBQU0sa0JBQWtCO0FBRXhCLElBQU0sY0FBYztBQUNwQixJQUFNLHVCQUF1QjtBQUM3QixJQUFNLGFBQWE7QUFFbkIsSUFBTSxpQkFBaUI7QUFldkIsSUFBTSxtQkFBNkI7QUFBQSxFQUNsQywyQkFBMkI7QUFBQSxFQUMzQixzQkFBc0I7QUFBQSxFQUN0QixnQkFBZ0I7QUFBQSxFQUNoQixnQkFBZ0I7QUFBQSxFQUVoQixjQUFjO0FBQUEsRUFFZCxnQkFBZ0I7QUFBQSxFQUNoQixrQkFBa0I7QUFBQSxFQUNsQix3QkFBd0I7QUFBQTtBQThCekIsb0NBQTZDLHVCQUFPO0FBQUEsRUFHN0MsU0FBUztBQUFBO0FBQ2QsY0FBUSxJQUFJO0FBQ1osWUFBTSxLQUFLO0FBQ1gsV0FBSyxjQUFjLElBQUksbUJBQW1CLEtBQUssS0FBSztBQUNwRCxXQUFLLDhCQUE4QixDQUFDLElBQUksUUFBUTtBQUMvQyx5QkFBaUIsSUFB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 384 | --------------------------------------------------------------------------------