├── .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 | 
25 |
26 | In version 1.0.4, add the language in the top right, like this:
27 | 
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 |
--------------------------------------------------------------------------------