diff --git a/plugin/auto_number.js b/plugin/auto_number.js index f53b96f5..aae32e94 100644 --- a/plugin/auto_number.js +++ b/plugin/auto_number.js @@ -1,7 +1,6 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("auto_number"); - - const base_css = ` +class autoNumberPlugin extends global._basePlugin { + beforeProcess = () => { + this.base_css = ` #write { counter-reset: write-h2 Figures Tables Fences; } h1 { counter-reset: write-h2 Figures Tables Fences; } h2 { counter-reset: write-h3 Figures Tables Fences; } @@ -24,7 +23,7 @@ } } ` - const content_css = ` + this.content_css = ` #write h2:before { counter-increment: write-h2; content: counter(write-h2) ". "; @@ -77,7 +76,7 @@ line-height: inherit; }` - const side_bar_css = ` + this.side_bar_css = ` .outline-content { counter-reset: outline-h2; } .outline-h1 { counter-reset: outline-h2; } .outline-h2 { counter-reset: outline-h3; } @@ -110,7 +109,7 @@ content: counter(outline-h2) "." counter(outline-h3) "." counter(outline-h4) "." counter(outline-h5) "." counter(outline-h6) " "; }` - const toc_css = ` + this.toc_css = ` .md-toc-content { counter-reset: toc-h2; } .md-toc-h1 { counter-reset: toc-h2; } .md-toc-h2 { counter-reset: toc-h3; } @@ -143,33 +142,33 @@ content: counter(toc-h2) "." counter(toc-h3) "." counter(toc-h4) "." counter(toc-h5) "." counter(toc-h6) " "; }` - const image_css = ` + this.image_css = ` #write p span.md-image.md-img-loaded::after { counter-increment: Figures; - content: "${config.NAME.image} " counter(write-h2) "-" counter(Figures); + content: "${this.config.NAMES.image} " counter(write-h2) "-" counter(Figures); font-family: monospace; display: block; text-align: center; margin: 4px 0; }` - const table_css = ` + this.table_css = ` #write figure.table-figure::after { counter-increment: Tables; - content: "${config.NAME.table} " counter(write-h2) "-" counter(Tables); + content: "${this.config.NAMES.table} " counter(write-h2) "-" counter(Tables); font-family: monospace; display: block; text-align: center; margin: 4px 0; }` - const fence_css = ` + this.fence_css = ` #write .md-fences { margin-bottom: 2.4em; } #write .md-fences::after { counter-increment: Fences; - content: "${config.NAME.fence} " counter(write-h2) "-" counter(Fences); + content: "${this.config.NAMES.fence} " counter(write-h2) "-" counter(Fences); position: absolute; width: 100%; text-align: center; @@ -178,43 +177,21 @@ font-size: 1.1em; z-index: 9; }` - - const removeStyle = () => { - const ele = document.getElementById(config.ID); - ele && ele.parentElement && ele.parentElement.removeChild(ele); - } - - const getStyleString = () => { - return [ - base_css, - (config.ENABLE_CONTENT) ? content_css : "", - (config.ENABLE_SIDE_BAR) ? side_bar_css : "", - (config.ENABLE_TOC) ? toc_css : "", - (config.ENABLE_IMAGE) ? image_css : "", - (config.ENABLE_TABLE) ? table_css : "", - (config.ENABLE_FENCE) ? fence_css : "", - ].join("\n") } - const insertStyle = toggle => { - if (toggle) { - config[toggle] = !config[toggle]; - removeStyle(); - } - - const css = getStyleString(); - global._pluginUtils.insertStyle(config.ID, css); + style = () => { + const textID = this.config.ID; + const text = this.getResultStyle(); + return {textID, text} } - insertStyle(); - - if (config.ENABLE_WHEN_EXPORT) { - const decoMixin = { + init = () => { + this.decoMixin = { inExport: false, beforeExport: (...args) => { this.inExport = true; - args[0].extraCss = `body {font-variant-ligatures: no-common-ligatures;} ` + getStyleString(); + args[0].extraCss = `body {font-variant-ligatures: no-common-ligatures;} ` + this.getStyleString(); }, afterGetHeaderMatrix: headers => { @@ -261,84 +238,114 @@ } } - global._pluginUtils.decorate( - () => (File && File.editor && File.editor.export && File.editor.export.exportToHTML), - File.editor.export, - "exportToHTML", - decoMixin.beforeExport, - null - ); - global._pluginUtils.decorate( - () => (File && File.editor && File.editor.library && File.editor.library.outline - && File.editor.library.outline.getHeaderMatrix), - File.editor.library.outline, - "getHeaderMatrix", - null, - decoMixin.afterGetHeaderMatrix - ); + this.callArgs = [ + { + arg_name: "禁用/启用大纲自动编号", + arg_value: "set_outline" + }, + { + arg_name: "禁用/启用正文自动编号", + arg_value: "set_content" + }, + { + arg_name: "禁用/启用TOC自动编号", + arg_value: "set_toc" + }, + { + arg_name: "禁用/启用表格自动编号", + arg_value: "set_table" + }, + { + arg_name: "禁用/启用图片自动编号", + arg_value: "set_image" + }, + { + arg_name: "禁用/启用代码块自动编号", + arg_value: "set_fence" + }, + ]; + + this.callMap = { + disable: this.removeStyle, + enable: this.insertStyle, + set_outline: () => this.insertStyle("ENABLE_SIDE_BAR"), + set_content: () => this.insertStyle("ENABLE_CONTENT"), + set_toc: () => this.insertStyle("ENABLE_TOC"), + set_table: () => this.insertStyle("ENABLE_TABLE"), + set_image: () => this.insertStyle("ENABLE_IMAGE"), + set_fence: () => this.insertStyle("ENABLE_FENCE"), + } } - //////////////////////// 以下是声明式插件系统代码 //////////////////////// - const callArgs = [ - { - arg_name: "禁用/启用大纲自动编号", - arg_value: "set_outline" - }, - { - arg_name: "禁用/启用正文自动编号", - arg_value: "set_content" - }, - { - arg_name: "禁用/启用TOC自动编号", - arg_value: "set_toc" - }, - { - arg_name: "禁用/启用表格自动编号", - arg_value: "set_table" - }, - { - arg_name: "禁用/启用图片自动编号", - arg_value: "set_image" - }, - { - arg_name: "禁用/启用代码块自动编号", - arg_value: "set_fence" - }, - ]; + process = () => { + this.init(); - const dynamicCallArgsGenerator = () => { + if (this.config.ENABLE_WHEN_EXPORT) { + this.utils.decorate( + () => (File && File.editor && File.editor.export && File.editor.export.exportToHTML), + File.editor.export, + "exportToHTML", + this.decoMixin.beforeExport, + null + ); + this.utils.decorate( + () => (File && File.editor && File.editor.library && File.editor.library.outline + && File.editor.library.outline.getHeaderMatrix), + File.editor.library.outline, + "getHeaderMatrix", + null, + this.decoMixin.afterGetHeaderMatrix + ); + } + } + + removeStyle = () => { + const ele = document.getElementById(this.config.ID); + ele && ele.parentElement && ele.parentElement.removeChild(ele); + } + + getStyleString = () => { + return [ + this.base_css, + (this.config.ENABLE_CONTENT) ? this.content_css : "", + (this.config.ENABLE_SIDE_BAR) ? this.side_bar_css : "", + (this.config.ENABLE_TOC) ? this.toc_css : "", + (this.config.ENABLE_IMAGE) ? this.image_css : "", + (this.config.ENABLE_TABLE) ? this.table_css : "", + (this.config.ENABLE_FENCE) ? this.fence_css : "", + ].join("\n") + } + + getResultStyle = toggle => { + if (toggle) { + this.config[toggle] = !this.config[toggle]; + this.removeStyle(); + } + + return this.getStyleString() + } + + insertStyle = toggle => { + const css = this.getResultStyle(toggle); + this.utils.insertStyle(this.config.ID, css); + } + + dynamicCallArgsGenerator = () => { let arg_name = "启用"; let arg_value = "enable"; - if (!!document.getElementById(config.ID)) { + if (!!document.getElementById(this.config.ID)) { arg_name = "禁用"; arg_value = "disable"; } return [{arg_name, arg_value}] } - const callMap = { - disable: removeStyle, - enable: insertStyle, - set_outline: () => insertStyle("ENABLE_SIDE_BAR"), - set_content: () => insertStyle("ENABLE_CONTENT"), - set_toc: () => insertStyle("ENABLE_TOC"), - set_table: () => insertStyle("ENABLE_TABLE"), - set_image: () => insertStyle("ENABLE_IMAGE"), - set_fence: () => insertStyle("ENABLE_FENCE"), - } - - const call = type => { - const func = callMap[type]; + call = type => { + const func = this.callMap[type]; func && func(); } - module.exports = { - call, - callArgs, - dynamicCallArgsGenerator, - meta: { - call - } - }; +} - console.log("auto_number.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: autoNumberPlugin +}; \ No newline at end of file diff --git a/plugin/collapse_paragraph.js b/plugin/collapse_paragraph.js index 9b60a32a..b4d645d6 100644 --- a/plugin/collapse_paragraph.js +++ b/plugin/collapse_paragraph.js @@ -1,9 +1,8 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("collapse_paragraph"); - - (() => { - const css = ` - #write .${config.CLASS_NAME}::after { +class collapseParagraphPlugin extends global._basePlugin { + style = () => { + const textID = "plugin-collapse-paragraph-style" + const text = ` + #write .${this.config.CLASS_NAME}::after { display: initial; content: "{\\2026}" !important; margin: 0 0.6rem; @@ -11,29 +10,47 @@ color: white; opacity: 0.6; background-color: gray; - } - `; - global._pluginUtils.insertStyle("plugin-collapse-paragraph-style", css); - })() + }`; + return {textID, text} + } - const callbackOtherPlugin = () => { - const outlinePlugin = global._pluginUtils.getPlugin("outline"); - outlinePlugin && outlinePlugin.meta.refresh(); + init = () => { + this.paragraphList = ["H1", "H2", "H3", "H4", "H5", "H6"]; + this.dynamicUtil = {target: null} } - const paragraphList = ["H1", "H2", "H3", "H4", "H5", "H6"]; + process = () => { + this.init(); - const toggle = (paragraph, display) => { - const idx = paragraphList.indexOf(paragraph.tagName); - const stop = paragraphList.slice(0, idx + 1); + document.getElementById("write").addEventListener("click", ev => { + if (!this.utils.metaKeyPressed(ev)) return; + const paragraph = ev.target.closest("h1, h2, h3, h4, h5, h6"); + if (!paragraph) return; + + document.activeElement.blur(); + const collapsed = paragraph.classList.contains(this.config.CLASS_NAME); + const list = ev.altKey ? (ev.shiftKey ? this.findAllSiblings(paragraph) : this.findSiblings(paragraph)) : [paragraph]; + list.forEach(ele => this.trigger(ele, collapsed)); + this.callbackOtherPlugin(); + }) + } + + callbackOtherPlugin = () => { + const outlinePlugin = this.utils.getPlugin("outline"); + outlinePlugin && outlinePlugin.refresh(); + } + + toggle = (paragraph, display) => { + const idx = this.paragraphList.indexOf(paragraph.tagName); + const stop = this.paragraphList.slice(0, idx + 1); let ele = paragraph.nextElementSibling; while (ele && stop.indexOf(ele.tagName) === -1) { - if (paragraphList.indexOf(ele.tagName) !== -1 - && ele.classList.contains(config.CLASS_NAME) + if (this.paragraphList.indexOf(ele.tagName) !== -1 + && ele.classList.contains(this.config.CLASS_NAME) && display === "") { ele.style.display = ""; - ele = toggle(ele, "none"); + ele = this.toggle(ele, "none"); continue } @@ -43,27 +60,26 @@ return ele; } - - const trigger = (paragraph, collapsed) => { + trigger = (paragraph, collapsed) => { if (collapsed) { - paragraph.classList.remove(config.CLASS_NAME); - toggle(paragraph, ""); + paragraph.classList.remove(this.config.CLASS_NAME); + this.toggle(paragraph, ""); } else { - paragraph.classList.add(config.CLASS_NAME); - toggle(paragraph, "none"); + paragraph.classList.add(this.config.CLASS_NAME); + this.toggle(paragraph, "none"); } } - const rollback = start => { - if (!document.querySelector(`#write > .${config.CLASS_NAME}`)) return; + rollback = start => { + if (!document.querySelector(`#write > .${this.config.CLASS_NAME}`)) return; let ele = start.closest("#write > [cid]"); const pList = []; while (ele) { - const idx = paragraphList.indexOf(ele.tagName); + const idx = this.paragraphList.indexOf(ele.tagName); if (idx !== -1) { - if (pList.length === 0 || (pList[pList.length - 1].idx > idx && ele.classList.contains(config.CLASS_NAME))) { + if (pList.length === 0 || (pList[pList.length - 1].idx > idx && ele.classList.contains(this.config.CLASS_NAME))) { pList.push({ele, idx}) if (pList[pList.length - 1].idx === 0) break; } @@ -73,14 +89,14 @@ if (pList.length > 0) { for (let i = pList.length - 1; i >= 0; i--) { - trigger(pList[i].ele, true); + this.trigger(pList[i].ele, true); } } } - const findSiblings = paragraph => { - const idx = paragraphList.indexOf(paragraph.tagName); - const stop = paragraphList.slice(0, idx); + findSiblings = paragraph => { + const idx = this.paragraphList.indexOf(paragraph.tagName); + const stop = this.paragraphList.slice(0, idx); const result = [paragraph]; ["previousElementSibling", "nextElementSibling"].forEach(direction => { @@ -96,27 +112,13 @@ return result; } - const findAllSiblings = paragraph => document.querySelectorAll(`#write ${paragraph.tagName}`); + findAllSiblings = paragraph => document.querySelectorAll(`#write ${paragraph.tagName}`); - document.getElementById("write").addEventListener("click", ev => { - if (!global._pluginUtils.metaKeyPressed(ev)) return; - const paragraph = ev.target.closest("h1, h2, h3, h4, h5, h6"); - if (!paragraph) return; - - document.activeElement.blur(); - const collapsed = paragraph.classList.contains(config.CLASS_NAME); - const list = ev.altKey ? (ev.shiftKey ? findAllSiblings(paragraph) : findSiblings(paragraph)) : [paragraph]; - list.forEach(ele => trigger(ele, collapsed)); - callbackOtherPlugin(); - }) - - //////////////////////// 以下是声明式插件系统代码 //////////////////////// - const dynamicUtil = {target: null} - const dynamicCallArgsGenerator = anchorNode => { + dynamicCallArgsGenerator = anchorNode => { const target = anchorNode.closest("#write h1,h2,h3,h4,h5,h6"); if (!target) return; - dynamicUtil.target = target; + this.dynamicUtil.target = target; return [ { @@ -134,7 +136,7 @@ ] } - const callArgs = [ + callArgs = [ { arg_name: "折叠全部章节", arg_value: "collapse_all" @@ -145,48 +147,40 @@ }, ]; - const dynamicCall = type => { - if (!dynamicUtil.target) return; + dynamicCall = type => { + if (!this.dynamicUtil.target) return; - const collapsed = dynamicUtil.target.classList.contains(config.CLASS_NAME); + const collapsed = this.dynamicUtil.target.classList.contains(this.config.CLASS_NAME); let list; if (type === "call_current") { - list = [dynamicUtil.target]; + list = [this.dynamicUtil.target]; } else if (type === "call_siblings") { - list = findSiblings(dynamicUtil.target); + list = this.findSiblings(this.dynamicUtil.target); } else if (type === "call_all_siblings") { - list = findAllSiblings(dynamicUtil.target); + list = this.findAllSiblings(this.dynamicUtil.target); } if (list) { - list.forEach(ele => trigger(ele, collapsed)); + list.forEach(ele => this.trigger(ele, collapsed)); } } - const call = type => { + call = type => { if (type === "collapse_all") { - for (let i = paragraphList.length - 1; i >= 0; i--) { - document.getElementsByTagName(paragraphList[i]).forEach(ele => trigger(ele, false)); + for (let i = this.paragraphList.length - 1; i >= 0; i--) { + document.getElementsByTagName(this.paragraphList[i]).forEach(ele => this.trigger(ele, false)); } } else if (type === "expand_all") { - paragraphList.forEach(tag => document.getElementsByTagName(tag).forEach(ele => trigger(ele, true))); + this.paragraphList.forEach(tag => document.getElementsByTagName(tag).forEach(ele => this.trigger(ele, true))); } else { - dynamicCall(type); + this.dynamicCall(type); } - callbackOtherPlugin(); + this.callbackOtherPlugin(); } +} - module.exports = { - call, - callArgs, - dynamicCallArgsGenerator, - meta: { - call, - trigger, - rollback, - } - }; +module.exports = { + plugin: collapseParagraphPlugin +}; - console.log("collapse_paragraph.js had been injected"); -})() \ No newline at end of file diff --git a/plugin/commander.js b/plugin/commander.js index 82465fd0..7d451d52 100644 --- a/plugin/commander.js +++ b/plugin/commander.js @@ -1,14 +1,16 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("commander"); - const SHELL = { - CMD_BASH: "cmd/bash", - POWER_SHELL: "powershell", - GIT_BASH: "gitbash", - WSL: "wsl", - }; +class commanderPlugin extends global._basePlugin { + beforeProcess() { + this.SHELL = { + CMD_BASH: "cmd/bash", + POWER_SHELL: "powershell", + GIT_BASH: "gitbash", + WSL: "wsl", + }; + } - (() => { - const modal_css = ` + style = () => { + const textID = "plugin-commander-style" + const text = ` #typora-commander { position: fixed; top: 30%; @@ -91,23 +93,24 @@ #typora-commander-form input:focus, pre:focus { outline: 0 } - ` - global._pluginUtils.insertStyle("plugin-commander-style", modal_css); + `; + return {textID, text} + } + html = () => { const windowOption = (File.isMac) ? `` : ` - - - - `; - const builtin = config.BUILTIN.map(ele => ``).join(""); - const builtinSelect = !config.USE_BUILTIN ? "" : ``; + + + `; + const builtin = this.config.BUILTIN.map(ele => ``).join(""); + const builtinSelect = !this.config.USE_BUILTIN ? "" : ``; const div = `
- + ${builtinSelect}
@@ -119,30 +122,116 @@ const searchPanel = document.getElementById("md-searchpanel"); searchPanel.parentNode.insertBefore(modal, searchPanel.nextSibling); - if (!config.USE_BUILTIN) { + if (!this.config.USE_BUILTIN) { document.getElementById('typora-commander').style.width = "500px"; document.querySelector("#typora-commander-form input").style.width = "80%"; document.querySelector("#typora-commander-form .typora-commander-commit").style.left = "375px"; } - })() + } + + hotkey = () => { + return [{ + hotkey: this.config.HOTKEY, + callback: this.call, + }] + } + + init = () => { + this.modal = { + modal: document.getElementById('typora-commander'), + input: document.querySelector("#typora-commander-form input"), + shellSelect: document.querySelector("#typora-commander-form .typora-commander-shell"), + builtinSelect: document.querySelector("#typora-commander-form .typora-commander-builtin"), + commit: document.querySelector("#typora-commander-form .typora-commander-commit"), + output: document.querySelector(".typora-commander-output"), + pre: document.querySelector(".typora-commander-output pre"), + } + + this.arg_value_prefix = "call_builtin-"; + this.callArgs = [{arg_name: "显示/隐藏", arg_value: "show"}]; + this.config.BUILTIN.forEach(builtin => { + if (builtin.name) { + this.callArgs.push({ + arg_name: `${builtin.name}`, + arg_value: this.arg_value_prefix + builtin.name + }) + } + }); + } + + process = () => { + this.init(); + + // 提供不同入口,让鼠标操作的用户不必切换回键盘操作 + this.modal.commit.addEventListener("click", ev => { + this.commit(); + ev.stopPropagation(); + ev.preventDefault(); + }, true); + + this.modal.input.addEventListener("input", () => { + const cmd = this.modal.input.value.trim(); + if (cmd) { + this.modal.commit.style.display = "block"; + } else { + this.modal.commit.style.display = "none"; + this.modal.builtinSelect.value = ""; + } + }) + + this.modal.shellSelect.addEventListener("change", () => this.modal.input.focus()); + + this.modal.modal.addEventListener("keydown", ev => { + switch (ev.key) { + case "Enter": + const input = ev.target.closest("input") + if (input) { + this.commit(); + ev.stopPropagation(); + ev.preventDefault(); + } + break + case "Escape": + ev.stopPropagation(); + ev.preventDefault(); + this.modal.modal.style.display = "none"; + break + case "Tab": + const targetClass = this.config.USE_BUILTIN ? ".typora-commander-builtin" : ".typora-commander-shell"; + const target = ev.target.closest(targetClass); + if (target) { + ev.stopPropagation(); + ev.preventDefault(); + this.modal.input.focus(); + } + break + } + }) + + if (this.config.USE_BUILTIN) { + this.modal.builtinSelect.addEventListener("change", () => { + const option = this.modal.builtinSelect.options[this.modal.builtinSelect.selectedIndex]; + this.modal.shellSelect.value = option.getAttribute("shell"); + this.modal.input.value = option.value; + this.modal.input.dispatchEvent(new CustomEvent('input')); + this.modal.input.focus(); + }) + } + + if (this.config.ALLOW_DRAG) { + this.utils.dragFixedModal(this.modal.input, this.modal.modal); + } - const modal = { - modal: document.getElementById('typora-commander'), - input: document.querySelector("#typora-commander-form input"), - shellSelect: document.querySelector("#typora-commander-form .typora-commander-shell"), - builtinSelect: document.querySelector("#typora-commander-form .typora-commander-builtin"), - commit: document.querySelector("#typora-commander-form .typora-commander-commit"), - output: document.querySelector(".typora-commander-output"), - pre: document.querySelector(".typora-commander-output pre"), } - const convertPath = (path, shell) => { + + convertPath = (path, shell) => { if (File.isMac) { return path } switch (shell) { - case SHELL.WSL: - case SHELL.GIT_BASH: + case this.SHELL.WSL: + case this.SHELL.GIT_BASH: path = path.replace(/\\/g, "/"); const tempList = path.split(":"); if (tempList.length !== 2) { @@ -150,50 +239,50 @@ } const disk = tempList[0].toLowerCase(); const remain = tempList[1]; - return (shell === SHELL.GIT_BASH) ? `/${disk}${remain}` : `/mnt/${disk}${remain}` - case SHELL.CMD_BASH: - case SHELL.POWER_SHELL: + return (shell === this.SHELL.GIT_BASH) ? `/${disk}${remain}` : `/mnt/${disk}${remain}` + case this.SHELL.CMD_BASH: + case this.SHELL.POWER_SHELL: default: return path } } - const getFilePath = global._pluginUtils.getFilePath; - const getFile = shell => convertPath(getFilePath(), shell); - const getFolder = shell => convertPath(global._pluginUtils.Package.Path.dirname(getFilePath()), shell); - const getMountFolder = shell => convertPath(File.getMountFolder(), shell); + getFilePath = this.utils.getFilePath; + getFile = shell => this.convertPath(this.getFilePath(), shell); + getFolder = shell => this.convertPath(this.utils.Package.Path.dirname(this.getFilePath()), shell); + getMountFolder = shell => this.convertPath(File.getMountFolder(), shell); - const replaceArgs = (cmd, shell) => { - const file = getFile(shell); - const folder = getFolder(shell); - const mount = getMountFolder(shell); + replaceArgs = (cmd, shell) => { + const file = this.getFile(shell); + const folder = this.getFolder(shell); + const mount = this.getMountFolder(shell); cmd = cmd.replace(/\$f/g, `"${file}"`); cmd = cmd.replace(/\$d/g, `"${folder}"`); cmd = cmd.replace(/\$m/g, `"${mount}"`); return cmd } - const getShellCommand = env => { + getShellCommand = env => { switch (env) { - case SHELL.GIT_BASH: + case this.SHELL.GIT_BASH: return `bash.exe -c` - case SHELL.POWER_SHELL: + case this.SHELL.POWER_SHELL: return `powershell /C` - case SHELL.WSL: + case this.SHELL.WSL: return `wsl.exe -e bash -c` default: return File.isMac ? `bash -c` : `cmd /C`; } } - const exec = (cmd, shell, resolve, reject) => { - const _shell = getShellCommand(shell); - const _cmd = replaceArgs(cmd, shell); - global._pluginUtils.Package.ChildProcess.exec( + exec = (cmd, shell, resolve, reject) => { + const _shell = this.getShellCommand(shell); + const _cmd = this.replaceArgs(cmd, shell); + this.utils.Package.ChildProcess.exec( `chcp 65001 | ${_shell} "${_cmd}"`, { encoding: 'utf8', - cwd: getFolder(), + cwd: this.getFolder(), }, (err, stdout, stderr) => { if (err || stderr.length) { @@ -206,142 +295,59 @@ }) } - const showStdout = stdout => { - modal.output.style.display = "block"; - modal.pre.classList.remove("error"); - modal.pre.textContent = stdout; + showStdout = stdout => { + this.modal.output.style.display = "block"; + this.modal.pre.classList.remove("error"); + this.modal.pre.textContent = stdout; } - const showStdErr = stderr => { - showStdout(stderr); - modal.pre.classList.add("error"); + showStdErr = stderr => { + this.showStdout(stderr); + this.modal.pre.classList.add("error"); } - const silentExec = (cmd, shell) => exec(cmd, shell, null, null); - const errorExec = (cmd, shell) => exec(cmd, shell, null, showStdErr); - const alwaysExec = (cmd, shell) => exec(cmd, shell, showStdout, showStdErr); + silentExec = (cmd, shell) => this.exec(cmd, shell, null, null); + errorExec = (cmd, shell) => this.exec(cmd, shell, null, this.showStdErr); + alwaysExec = (cmd, shell) => this.exec(cmd, shell, this.showStdout, this.showStdErr); - const commit = () => { - const cmd = modal.input.value; + commit = () => { + const cmd = this.modal.input.value; if (!cmd) { - showStdErr("command is empty"); + this.showStdErr("command is empty"); return } - const option = modal.shellSelect.options[modal.shellSelect.selectedIndex]; + const option = this.modal.shellSelect.options[this.modal.shellSelect.selectedIndex]; const shell = option.value; - alwaysExec(cmd, shell); + this.alwaysExec(cmd, shell); } - // 提供不同入口,让鼠标操作的用户不必切换回键盘操作 - modal.commit.addEventListener("click", ev => { - commit(); - ev.stopPropagation(); - ev.preventDefault(); - }, true); - - modal.input.addEventListener("input", ev => { - const cmd = modal.input.value.trim(); - if (cmd) { - modal.commit.style.display = "block"; - } else { - modal.commit.style.display = "none"; - modal.builtinSelect.value = ""; - } - }) - - modal.shellSelect.addEventListener("change", ev => modal.input.focus()); - - modal.modal.addEventListener("keydown", ev => { - switch (ev.key) { - case "Enter": - const input = ev.target.closest("input") - if (input) { - commit(); - ev.stopPropagation(); - ev.preventDefault(); - } - break - case "Escape": - ev.stopPropagation(); - ev.preventDefault(); - modal.modal.style.display = "none"; - break - case "Tab": - const targetClass = config.USE_BUILTIN ? ".typora-commander-builtin" : ".typora-commander-shell"; - const target = ev.target.closest(targetClass); - if (target) { - ev.stopPropagation(); - ev.preventDefault(); - modal.input.focus(); - } - break - } - }) - - if (config.USE_BUILTIN) { - modal.builtinSelect.addEventListener("change", ev => { - const option = modal.builtinSelect.options[modal.builtinSelect.selectedIndex]; - modal.shellSelect.value = option.getAttribute("shell"); - modal.input.value = option.value; - modal.input.dispatchEvent(new CustomEvent('input')); - modal.input.focus(); - }) - } - - if (config.ALLOW_DRAG) { - global._pluginUtils.dragFixedModal(modal.input, modal.modal); - } - - const arg_value_prefix = "call_builtin-"; - - const quickExec = (cmd, shell) => { - switch (config.QUICK_EXEC_SHOW) { + quickExec = (cmd, shell) => { + switch (this.config.QUICK_EXEC_SHOW) { case "always": - return alwaysExec(cmd, shell) + return this.alwaysExec(cmd, shell) case "error": - return errorExec(cmd, shell) + return this.errorExec(cmd, shell) case "silent": - return silentExec(cmd, shell) + return this.silentExec(cmd, shell) } } - const callArgs = [{arg_name: "显示/隐藏", arg_value: "show"}]; - config.BUILTIN.forEach(builtin => { - if (builtin.name) { - callArgs.push({ - arg_name: `${builtin.name}`, - arg_value: arg_value_prefix + builtin.name - }) - } - }); - - const call = (type = "show") => { + call = (type = "show") => { if (type === "show") { - if (modal.modal.style.display === "block") { - modal.modal.style.display = "none"; + if (this.modal.modal.style.display === "block") { + this.modal.modal.style.display = "none"; } else { - modal.modal.style.display = "block"; - modal.input.select(); + this.modal.modal.style.display = "block"; + this.modal.input.select(); } - } else if (type.startsWith(arg_value_prefix)) { - const name = type.slice(arg_value_prefix.length); - const builtin = config.BUILTIN.find(builtin => builtin.name === name); - builtin && quickExec(builtin.cmd, builtin.shell); + } else if (type.startsWith(this.arg_value_prefix)) { + const name = type.slice(this.arg_value_prefix.length); + const builtin = this.config.BUILTIN.find(builtin => builtin.name === name); + builtin && this.quickExec(builtin.cmd, builtin.shell); } } +} - global._pluginUtils.registerWindowHotkey(config.HOTKEY, call); - - module.exports = { - call, - callArgs, - meta: { - call, - silentExec, - errorExec, - alwaysExec, - } - }; - - console.log("commander.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: commanderPlugin +}; \ No newline at end of file diff --git a/plugin/datatables/index.js b/plugin/datatables/index.js index 12f0fa00..f13f5665 100644 --- a/plugin/datatables/index.js +++ b/plugin/datatables/index.js @@ -1,9 +1,7 @@ -(() => { - (() => { - const cssFilepath = global._pluginUtils.joinPath("./plugin/datatables/resource/datatables.min.css"); - global._pluginUtils.insertStyleFile("plugin-datatables-common-style", cssFilepath); - - const css = ` +class datatablesPlugin extends global._basePlugin { + style = () => { + const textID = "plugin-datatables-custom-style"; + const text = ` #write figure select, #write figure input { border: 1px solid #ddd; @@ -25,27 +23,29 @@ .dataTables_wrapper .dataTables_info { padding-top: 0.25em; - } - ` - global._pluginUtils.insertStyle("plugin-datatables-custom-style", css); + }`; - const jsFilepath = global._pluginUtils.joinPath("./plugin/datatables/resource/datatables.min.js"); - $.getScript(`file:///${jsFilepath}`).then(() => console.log("datatables.min.js has inserted")); - })() + const fileID = "plugin-datatables-common-style"; + const file = "./plugin/datatables/resource/datatables.min.css"; + return {textID, text, fileID, file} + } - const config = global._pluginUtils.getPluginSetting("datatables"); + html = () => { + const jsFilepath = this.utils.joinPath("./plugin/datatables/resource/datatables.min.js"); + $.getScript(`file:///${jsFilepath}`).then(() => console.log("datatables.min.js has inserted")); + } - const dataTablesConfig = (() => { - const cfg = { - paging: config.PAGING, - ordering: config.ORDERING, - searching: config.SEARCHING, - pageLength: config.PAGE_LENGTH, - scrollCollapse: config.SROLL_COLLAPSE, + init = () => { + this.dataTablesConfig = { + paging: this.config.PAGING, + ordering: this.config.ORDERING, + searching: this.config.SEARCHING, + pageLength: this.config.PAGE_LENGTH, + scrollCollapse: this.config.SROLL_COLLAPSE, processing: true, search: { - caseInsensitive: config.CASE_INSENSITIVE, - regex: config.REGEX, + caseInsensitive: this.config.CASE_INSENSITIVE, + regex: this.config.REGEX, }, language: { "processing": "处理中...", @@ -70,26 +70,53 @@ "thousands": "." } }; - - if (config.SCROLLY > 0) { - cfg["scrollY"] = config.SCROLLY; + if (this.config.SCROLLY > 0) { + this.dataTablesConfig = this.config.SCROLLY; } - if (!config.DEFAULT_ORDER) { - cfg["order"] = []; + if (!this.config.DEFAULT_ORDER) { + this.dataTablesConfig["order"] = []; } - return cfg - })() - let tableList = []; + this.tableList = []; + + this.dynamicUtil = {target: null, uuid: ""} + } + + process = () => { + this.init(); + + this.utils.decorateOpenFile(null, () => { + this.tableList.forEach(table => table.table.api().destroy()); + this.tableList = []; + }) - const addTfoot = $table => { - const th = $table.find("thead th"); - const list = [...th].map(ele => `${ele.textContent}: `); - const tfoot = `${list.join("")}`; - $table.append(tfoot); + this.utils.decorate( + () => (File && File.editor && File.editor.tableEdit && File.editor.tableEdit.showTableEdit), + File.editor.tableEdit, + "showTableEdit", + (...args) => { + if (!args[0]) return; + const table = args[0].find("table"); + if (table.length === 0) return + + const uuid = table.attr("table-uuid"); + const idx = this.tableList.findIndex(table => table.uuid === uuid); + if (idx !== -1) { + return this.utils.stopCallError + } + }, + null + ) } - const appendFilter = dataTable => { + // addTfoot = $table => { + // const th = $table.find("thead th"); + // const list = [...th].map(ele => `${ele.textContent}: `); + // const tfoot = `${list.join("")}`; + // $table.append(tfoot); + // } + + appendFilter = dataTable => { dataTable.columns().flatten().each(function (colIdx) { const select = $(" - + @@ -155,88 +158,250 @@ searchModal.innerHTML = div; const quickOpenNode = document.getElementById("typora-quick-open"); quickOpenNode.parentNode.insertBefore(searchModal, quickOpenNode.nextSibling); - })() - - const entities = { - write: document.getElementById("write"), - modal: document.getElementById('plugin-multi-highlighter'), - input: document.querySelector("#plugin-multi-highlighter-input input"), - runButton: document.querySelector("#plugin-multi-highlighter-input .run-highlight"), - caseOption: document.querySelector(".plugin-multi-highlighter-option-btn"), - result: document.getElementById("plugin-multi-highlighter-result"), } - const collapsePlugin = global._pluginUtils.getPlugin("collapse_paragraph"); - const truncatePlugin = global._pluginUtils.getPlugin("truncate_text"); - const fenceEnhancePlugin = global._pluginUtils.getPlugin("fence_enhance"); - const compatibleOtherPlugin = target => { - if (!target) return; + hotkey = () => { + return [{ + hotkey: this.config.HOTKEY, + callback: this.call, + }] + } - collapsePlugin && collapsePlugin.meta && collapsePlugin.meta.rollback && collapsePlugin.meta.rollback(target); - truncatePlugin && truncatePlugin.meta && truncatePlugin.meta.rollback && truncatePlugin.meta.rollback(target); + init = () => { + this.entities = { + write: document.getElementById("write"), + modal: document.getElementById('plugin-multi-highlighter'), + input: document.querySelector("#plugin-multi-highlighter-input input"), + runButton: document.querySelector("#plugin-multi-highlighter-input .run-highlight"), + caseOption: document.querySelector(".plugin-multi-highlighter-option-btn"), + result: document.getElementById("plugin-multi-highlighter-result"), + } + + this.multiHighlighter = new multiHighlighter(this.utils); + this.fenceMultiHighlighterList = []; // 为了解决fence惰性加载的问题 + + this.lastHighlightFilePath = "" + this.showMarkerInfo = { + idxOfFence: -1, + idxOfWrite: -1, + } + this.hasMarker = false + this.decoMixin = { + before: (...args) => { + const cid = args[0]; + if (!cid || this.multiHighlighter.length() === 0) return; + + const marker = this.entities.write.querySelector(`.md-fences[cid=${cid}] marker`); + this.hasMarker = !!marker; + }, + after: (result, ...args) => { + const cid = args[0]; + if (!cid || !this.hasMarker || this.multiHighlighter.length() === 0) return; + + this.hasMarker = false; + + const fence = this.entities.write.querySelector(`.md-fences[cid=${cid}]`); + if (!fence) return; + + const tokens = this.multiHighlighter.getTokens(); + if (this.config.USE_LIST_THRESHOLD > tokens.length + || this.config.CLEAR_LIST_THRESHOLD > 0 && this.fenceMultiHighlighterList.length === this.config.CLEAR_LIST_THRESHOLD) { + this.clearFenceMultiHighlighterList(); + this.multiHighlighter.removeHighlight(); + this.multiHighlighter.highlight(); + if (this.showMarkerInfo.idxOfWrite !== -1) { + this.getAndShowMarker(this.entities.write, this.showMarkerInfo.idxOfWrite); + this.showMarkerInfo.idxOfWrite = -1; + } + } else { + const fenceMultiHighlighter = new multiHighlighter(this.utils); + fenceMultiHighlighter.new(tokens, fence, this.config.CASE_SENSITIVE, "plugin-search-hit"); + fenceMultiHighlighter.highlight(); + this.fenceMultiHighlighterList.push(fenceMultiHighlighter); + if (this.showMarkerInfo.idxOfFence !== -1) { + this.getAndShowMarker(fence, this.showMarkerInfo.idxOfFence); + this.showMarkerInfo.idxOfFence = -1; + } + } + } + } } - const compatibleFenceEnhancePlugin = fence => { - fence && fenceEnhancePlugin && fenceEnhancePlugin.meta - && fenceEnhancePlugin.meta.expandFence && fenceEnhancePlugin.meta.expandFence(fence); + + process = () => { + this.init(); + + this.utils.decorateAddCodeBlock(this.decoMixin.before, this.decoMixin.after); + + if (this.config.RESEARCH_WHILE_OPEN_FILE) { + this.utils.decorateOpenFile(null, () => { + (this.entities.modal.style.display === "block") && setTimeout(this.highlight, 300) + }) + } + + this.entities.input.addEventListener("keydown", ev => { + if (ev.key === "Enter") { + ev.stopPropagation(); + ev.preventDefault(); + this.highlight(); + } else if (ev.key === "Escape") { + ev.stopPropagation(); + ev.preventDefault(); + this.clearHighlight(); + this.refreshFences(); + this.hide(); + } + }) + + this.entities.caseOption.addEventListener("click", ev => { + this.entities.caseOption.classList.toggle("select"); + this.config.CASE_SENSITIVE = !this.config.CASE_SENSITIVE; + ev.preventDefault(); + ev.stopPropagation(); + }) + + if (this.config.SHOW_RUN_BUTTON) { + this.entities.runButton.addEventListener("click", ev => { + this.highlight(); + ev.preventDefault(); + ev.stopPropagation(); + }) + } + + if (this.config.REMOVE_WHEN_EDIT) { + document.querySelector("content").addEventListener("mousedown", ev => { + if (this.multiHighlighter.length() !== 0 && !ev.target.closest("#plugin-multi-highlighter")) { + this.clearHighlight(); + this.refreshFences(); + } + }, true) + } + + if (this.config.ALLOW_DRAG) { + this.utils.dragFixedModal(this.entities.input, this.entities.modal); + } + + this.entities.result.addEventListener("mousedown", ev => { + const target = ev.target.closest(".plugin-multi-highlighter-result-item"); + if (!target) return; + + ev.stopPropagation(); + ev.preventDefault(); + + // 当用户切换文档时 + if (this.utils.getFilePath() !== this.lastHighlightFilePath) { + this.highlight(); + return; + } + + const idx = target.getAttribute("idx"); + const className = `plugin-search-hit${idx}` + let resultList = document.getElementsByClassName(className); + + // 如果被刷新掉了,重新请求一次 + if (resultList.length === 0) { + const success = this.highlight(false); + if (!success) return; + resultList = document.getElementsByClassName(className); + } + + let targetIdx = parseInt(target.getAttribute("cur")); + + let nextIdx; + if (ev.button === 0) { // 鼠标左键 + nextIdx = (targetIdx === resultList.length - 1) ? 0 : targetIdx + 1; + } else if (ev.button === 2) { //鼠标右键 + nextIdx = (targetIdx === 0 || targetIdx === -1) ? resultList.length - 1 : targetIdx - 1; + } + + const next = resultList[nextIdx]; + if (!next) { + this.highlight(); + return; + } + + this.utils.showHiddenElementByPlugin(next); + + this.showMarkerInfo.idxOfWrite = this.whichMarker(this.entities.write, next); + + const fence = next.closest("#write .md-fences"); + if (fence && !fence.classList.contains("modeLoaded")) { + this.showMarkerInfo.idxOfFence = this.whichMarker(fence, next); + // scroll到Fence,触发File.editor.fences.addCodeBlock函数,接下来的工作就交给他了 + this.scroll(next); + } else { + this.handleHiddenElement(next); + this.scroll(next); + this.showIfNeed(next); + } + target.setAttribute("cur", nextIdx + ""); + if (this.config.SHOW_CURRENT_INDEX) { + const searcher = this.multiHighlighter.getHighlighter(idx); + if (searcher) { + target.innerText = `${searcher.token.text} (${nextIdx + 1}/${searcher.matches.length})` + } + } + }) } - const multiHighlighterClass = global._pluginUtils.requireFile("./plugin/multi_highlighter/multi_highlighter.js").multiHighlighter; - const multiHighlighter = new multiHighlighterClass(); - let fenceMultiHighlighterList = []; // 为了解决fence惰性加载的问题 + compatibleFenceEnhancePlugin = fence => { + if (!fence) return; + const fenceEnhancePlugin = this.utils.getPlugin("fence_enhance"); + fenceEnhancePlugin && fenceEnhancePlugin.expandFence(fence); + } - const clearFenceMultiHighlighterList = () => { + clearFenceMultiHighlighterList = () => { console.log("clearFenceMultiHighlighterList"); - fenceMultiHighlighterList.forEach(highlighter => highlighter.clear()); - fenceMultiHighlighterList = []; + this.fenceMultiHighlighterList.forEach(highlighter => highlighter.clear()); + this.fenceMultiHighlighterList = []; } - const clearHighlight = () => { - multiHighlighter.clear(); - clearFenceMultiHighlighterList(); - entities.write.querySelectorAll(".plugin-multi-highlighter-bar").forEach( + clearHighlight = () => { + this.multiHighlighter.clear(); + this.clearFenceMultiHighlighterList(); + this.entities.write.querySelectorAll(".plugin-multi-highlighter-bar").forEach( ele => ele && ele.parentElement && ele.parentElement.removeChild(ele)); } - const doSearch = (keyArr, refreshResult = true) => { - clearHighlight(); + doSearch = (keyArr, refreshResult = true) => { + this.clearHighlight(); - multiHighlighter.new(keyArr, entities.write, config.CASE_SENSITIVE, "plugin-search-hit"); - multiHighlighter.highlight(); + this.multiHighlighter.new(keyArr, this.entities.write, this.config.CASE_SENSITIVE, "plugin-search-hit"); + this.multiHighlighter.highlight(); if (refreshResult) { - const itemList = multiHighlighter.getList().map((searcher, idx) => { - const color = (idx < config.STYLE_COLOR.length) ? config.STYLE_COLOR[idx] : config.DEFAULT_COLOR; + const itemList = this.multiHighlighter.getList().map((searcher, idx) => { + const color = (idx < this.config.STYLE_COLOR.length) ? this.config.STYLE_COLOR[idx] : this.config.DEFAULT_COLOR; return `
${searcher.token.text} (${searcher.matches.length})
`; }) - entities.result.innerHTML = itemList.join(""); + this.entities.result.innerHTML = itemList.join(""); } - entities.result.style.display = ""; + this.entities.result.style.display = ""; } - const refreshFences = () => { + refreshFences = () => { console.log("refreshFences"); for (let id in File.editor.fences.queue) { File.editor.fences.queue[id].refresh(); } } - const getKeyArr = () => { - const value = entities.input.value; + getKeyArr = () => { + const value = this.entities.input.value; if (!value) return; - return value.split(config.SEPARATOR).filter(Boolean) + return value.split(this.config.SEPARATOR).filter(Boolean) } - let lastHighlightFilePath; - const highlight = (refreshResult = true) => { - lastHighlightFilePath = global._pluginUtils.getFilePath(); - const keyArr = getKeyArr(); + + highlight = (refreshResult = true) => { + this.lastHighlightFilePath = this.utils.getFilePath(); + const keyArr = this.getKeyArr(); if (!keyArr) return false; - doSearch(keyArr, refreshResult); + this.doSearch(keyArr, refreshResult); return true; } - const handleHiddenElement = marker => { + handleHiddenElement = marker => { const image = marker.closest(`#write span[md-inline="image"]`); if (image) { image.classList.add("md-expand"); @@ -247,30 +412,30 @@ } const fence = marker.closest("#write .md-fences"); if (fence) { - compatibleFenceEnhancePlugin(fence); + this.compatibleFenceEnhancePlugin(fence); } } - const scroll = marker => { + scroll = marker => { const totalHeight = window.innerHeight || document.documentElement.clientHeight; File.editor.focusAndRestorePos(); File.editor.selection.scrollAdjust(marker, totalHeight / 2); File.isFocusMode && File.editor.updateFocusMode(false); } - // 已废弃 - const scroll2 = marker => { - requestAnimationFrame(() => marker.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"})); - } + // // 已废弃 + // scroll2 = marker => { + // requestAnimationFrame(() => marker.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"})); + // } - const showIfNeed = marker => { - if (config.SHOW_KEYWORD_OUTLINE) { + showIfNeed = marker => { + if (this.config.SHOW_KEYWORD_OUTLINE) { document.querySelectorAll(".plugin-multi-highlighter-move").forEach(ele => ele.classList.remove("plugin-multi-highlighter-move")); marker.classList.add("plugin-multi-highlighter-move"); } - if (config.SHOW_KEYWORD_BAR) { - const writeRect = entities.write.getBoundingClientRect(); + if (this.config.SHOW_KEYWORD_BAR) { + const writeRect = this.entities.write.getBoundingClientRect(); const markerRect = marker.getBoundingClientRect(); const bar = document.createElement("div"); @@ -284,7 +449,7 @@ } } - const whichMarker = (parent, marker) => { + whichMarker = (parent, marker) => { const markers = parent.getElementsByTagName("marker"); for (let idx = 0; idx < markers.length; idx++) { if (markers[idx] === marker) { @@ -294,205 +459,89 @@ return -1 } - const getMarker = (parent, idx) => { + getMarker = (parent, idx) => { const markers = parent.querySelectorAll("marker"); if (markers) { return markers[idx]; } } - const hide = () => { - clearHighlight(); - entities.modal.style.display = "none"; - } - - entities.input.addEventListener("keydown", ev => { - if (ev.key === "Enter") { - ev.stopPropagation(); - ev.preventDefault(); - highlight(); - } else if (ev.key === "Escape") { - ev.stopPropagation(); - ev.preventDefault(); - clearHighlight(); - refreshFences(); - hide(); - } - }) - - entities.caseOption.addEventListener("click", ev => { - entities.caseOption.classList.toggle("select"); - config.CASE_SENSITIVE = !config.CASE_SENSITIVE; - ev.preventDefault(); - ev.stopPropagation(); - }) - - if (config.SHOW_RUN_BUTTON) { - entities.runButton.addEventListener("click", ev => { - highlight(); - ev.preventDefault(); - ev.stopPropagation(); - }) + hide = () => { + this.clearHighlight(); + this.entities.modal.style.display = "none"; } - if (config.REMOVE_WHEN_EDIT) { - document.querySelector("content").addEventListener("mousedown", ev => { - if (multiHighlighter.length() !== 0 && !ev.target.closest("#plugin-multi-highlighter")) { - clearHighlight(); - refreshFences(); + getAndShowMarker = (parent, idx) => { + setTimeout(() => { + const nthMarker = this.getMarker(parent, idx); + if (nthMarker) { + this.scroll(nthMarker); + this.showIfNeed(nthMarker); } - }, true) - } - - const showMarkerInfo = { - idxOfFence: -1, - idxOfWrite: -1, + }, 120); } - entities.result.addEventListener("mousedown", ev => { - const target = ev.target.closest(".plugin-multi-highlighter-result-item"); - if (!target) return; - - ev.stopPropagation(); - ev.preventDefault(); - - // 当用户切换文档时 - if (global._pluginUtils.getFilePath() !== lastHighlightFilePath) { - highlight(); - return; - } - - const idx = target.getAttribute("idx"); - const className = `plugin-search-hit${idx}` - let resultList = document.getElementsByClassName(className); - - // 如果被刷新掉了,重新请求一次 - if (resultList.length === 0) { - const success = highlight(false); - if (!success) return; - resultList = document.getElementsByClassName(className); - } - - let targetIdx = parseInt(target.getAttribute("cur")); - let nextIdx; - if (ev.button === 0) { // 鼠标左键 - nextIdx = (targetIdx === resultList.length - 1) ? 0 : targetIdx + 1; - } else if (ev.button === 2) { //鼠标右键 - nextIdx = (targetIdx === 0 || targetIdx === -1) ? resultList.length - 1 : targetIdx - 1; + call = () => { + if (this.entities.modal.style.display === "block") { + this.hide(); + } else { + this.entities.modal.style.display = "block"; + this.entities.input.select(); } + } +} - const next = resultList[nextIdx]; - if (!next) { - highlight(); - return; - } +class multiHighlighter { + constructor(utils) { + this.highlighterList = [] + this.InstantSearch = utils.requireFilePath("./plugin/multi_highlighter/highlighter.js").InstantSearch; + } - compatibleOtherPlugin(next); + _newHighlighter(root, key, caseSensitive, className) { + return new this.InstantSearch( + root, // root + {text: key, caseSensitive: caseSensitive, className: className}, //token + true, // scrollToResult + className, // defaultClassName + caseSensitive, // defaultCaseSensitive + ) + } - showMarkerInfo.idxOfWrite = whichMarker(entities.write, next); + new(keyArr, root, caseSensitive, className) { + this.highlighterList = keyArr.map((key, idx) => this._newHighlighter(root, key, caseSensitive, className + idx)); + } - const fence = next.closest("#write .md-fences"); - if (fence && !fence.classList.contains("modeLoaded")) { - showMarkerInfo.idxOfFence = whichMarker(fence, next); - // scroll到Fence,触发File.editor.fences.addCodeBlock函数,接下来的工作就交给他了 - scroll(next); - } else { - handleHiddenElement(next); - scroll(next); - showIfNeed(next); - } - target.setAttribute("cur", nextIdx + ""); - if (config.SHOW_CURRENT_INDEX) { - const searcher = multiHighlighter.getHighlighter(idx); - if (searcher) { - target.innerText = `${searcher.token.text} (${nextIdx + 1}/${searcher.matches.length})` - } - } - }) + highlight() { + this.highlighterList.forEach(highlighter => highlighter.highlight()); + } - if (config.ALLOW_DRAG) { - global._pluginUtils.dragFixedModal(entities.input, entities.modal); + removeHighlight() { + this.highlighterList.forEach(highlighter => highlighter.removeHighlight()); } - const getAndShowMarker = (parent, idx) => { - setTimeout(() => { - const nthMarker = getMarker(parent, idx); - if (nthMarker) { - scroll(nthMarker); - showIfNeed(nthMarker); - } - }, 120); + clear() { + this.removeHighlight(); + this.highlighterList = []; } - const decoMixin = { - hasMarker: false, - before: (...args) => { - const cid = args[0]; - if (!cid || multiHighlighter.length() === 0) return; - - const marker = entities.write.querySelector(`.md-fences[cid=${cid}] marker`); - this.hasMarker = !!marker; - }, - after: (result, ...args) => { - const cid = args[0]; - if (!cid || !this.hasMarker || multiHighlighter.length() === 0) return; - - this.hasMarker = false; - - const fence = entities.write.querySelector(`.md-fences[cid=${cid}]`); - if (!fence) return; - - const tokens = multiHighlighter.getTokens(); - if (config.USE_LIST_THRESHOLD > tokens.length - || config.CLEAR_LIST_THRESHOLD > 0 && fenceMultiHighlighterList.length === config.CLEAR_LIST_THRESHOLD) { - clearFenceMultiHighlighterList(); - multiHighlighter.removeHighlight(); - multiHighlighter.highlight(); - if (showMarkerInfo.idxOfWrite !== -1) { - getAndShowMarker(entities.write, showMarkerInfo.idxOfWrite); - showMarkerInfo.idxOfWrite = -1; - } - } else { - const fenceMultiHighlighter = new multiHighlighterClass(); - fenceMultiHighlighter.new(tokens, fence, config.CASE_SENSITIVE, "plugin-search-hit"); - fenceMultiHighlighter.highlight(); - fenceMultiHighlighterList.push(fenceMultiHighlighter); - if (showMarkerInfo.idxOfFence !== -1) { - getAndShowMarker(fence, showMarkerInfo.idxOfFence); - showMarkerInfo.idxOfFence = -1; - } - } - } + length() { + return this.highlighterList.length } - global._pluginUtils.decorateAddCodeBlock(decoMixin.before, decoMixin.after); + getList() { + return this.highlighterList + } - if (config.RESEARCH_WHILE_OPEN_FILE) { - global._pluginUtils.decorateOpenFile(null, () => { - (entities.modal.style.display === "block") && setTimeout(highlight, 300) - }) + getHighlighter(idx) { + return this.highlighterList[idx] } - const call = () => { - if (entities.modal.style.display === "block") { - hide(); - } else { - entities.modal.style.display = "block"; - entities.input.select(); - } + getTokens() { + return this.highlighterList.map(highlighter => highlighter.token.text) } +} - global._pluginUtils.registerWindowHotkey(config.HOTKEY, call); - module.exports = { - call, - meta: { - call, - hide, - highlight, - doSearch, - clearHighlight, - } - }; - console.log("multi_highlighter.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: multiHighlighterPlugin +}; diff --git a/plugin/multi_highlighter/multi_highlighter.js b/plugin/multi_highlighter/multi_highlighter.js deleted file mode 100644 index 773f1198..00000000 --- a/plugin/multi_highlighter/multi_highlighter.js +++ /dev/null @@ -1,52 +0,0 @@ -const {InstantSearch} = global._pluginUtils.requireFile("./plugin/multi_highlighter/highlighter.js"); - -class multiHighlighter { - constructor() { - this.highlighterList = [] - } - - _newHighlighter(root, key, caseSensitive, className) { - return new InstantSearch( - root, // root - {text: key, caseSensitive: caseSensitive, className: className}, //token - true, // scrollToResult - className, // defaultClassName - caseSensitive, // defaultCaseSensitive - ) - } - - new(keyArr, root, caseSensitive, className) { - this.highlighterList = keyArr.map((key, idx) => this._newHighlighter(root, key, caseSensitive, className + idx)); - } - - highlight() { - this.highlighterList.forEach(highlighter => highlighter.highlight()); - } - - removeHighlight() { - this.highlighterList.forEach(highlighter => highlighter.removeHighlight()); - } - - clear() { - this.removeHighlight(); - this.highlighterList = []; - } - - length() { - return this.highlighterList.length - } - - getList() { - return this.highlighterList - } - - getHighlighter(idx) { - return this.highlighterList[idx] - } - - getTokens() { - return this.highlighterList.map(highlighter => highlighter.token.text) - } -} - -module.exports = {multiHighlighter}; diff --git a/plugin/outline.js b/plugin/outline.js index a9991d5b..85085b20 100644 --- a/plugin/outline.js +++ b/plugin/outline.js @@ -1,8 +1,7 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("outline"); - - (() => { - const modal_css = ` +class outlinePlugin extends global._basePlugin { + style = () => { + const textID = "plugin-outline-style" + const text = ` #plugin-outline { position: fixed; display: none; @@ -59,11 +58,13 @@ text-align: center; } ` - global._pluginUtils.insertStyle("plugin-outline-style", modal_css); + return {textID, text} + } - const all_button = (config.USE_ALL) ? `
` : ""; - const class_name = (config.SHOW_HIDDEN) ? "ion-eye" : "ion-eye-disabled"; - const hint = (config.SHOW_HIDDEN) ? "显示被其他插件隐藏的元素" : "不显示被其他插件隐藏的元素"; + html = () => { + const all_button = (this.config.USE_ALL) ? `
` : ""; + const class_name = (this.config.SHOW_HIDDEN) ? "ion-eye" : "ion-eye-disabled"; + const hint = (this.config.SHOW_HIDDEN) ? "显示被其他插件隐藏的元素" : "不显示被其他插件隐藏的元素"; const modal = document.createElement("div"); modal.id = 'plugin-outline'; @@ -83,187 +84,76 @@ ` document.querySelector("header").appendChild(modal); - })() - - const entities = { - modal: document.getElementById("plugin-outline"), - header: document.querySelector("#plugin-outline .plugin-outline-header"), - list: document.querySelector("#plugin-outline .plugin-outline-list"), - footer: document.querySelector("#plugin-outline .plugin-outline-footer"), - move: document.querySelector(`#plugin-outline .plugin-outline-icon[Type="move"]`), } - - class _collectUtil { - constructor() { - this.paragraphIdx = 0; - this.tableIdx = 0; - this.imageIdx = 0; - this.fenceIdx = 0; - this.collection = {table: [], image: [], fence: []}; + init = () => { + this.entities = { + modal: document.getElementById("plugin-outline"), + header: document.querySelector("#plugin-outline .plugin-outline-header"), + list: document.querySelector("#plugin-outline .plugin-outline-list"), + footer: document.querySelector("#plugin-outline .plugin-outline-footer"), + move: document.querySelector(`#plugin-outline .plugin-outline-icon[Type="move"]`), } + this.collectUtil = new _collectUtil(this.config, this.entities); + } - clear() { - this.paragraphIdx = this.tableIdx = this.imageIdx = this.fenceIdx = 0; - this.collection = {table: [], image: [], fence: []}; - } + process = () => { + this.init(); - collect() { - this.clear(); - const write = document.querySelector("#write"); - for (let ele = write.firstElementChild; ele; ele = ele.nextElementSibling) { - if (!config.SHOW_HIDDEN && ele.style.display === "none") { - continue - } + this.utils.dragFixedModal(this.entities.move, this.entities.modal, false); - const tagName = ele.tagName; - if (tagName === "H1") { - this.paragraphIdx = 0; - this.tableIdx = this.imageIdx = this.fenceIdx = 0; - continue - } else if (tagName === "H2") { - this.paragraphIdx++; - this.tableIdx = this.imageIdx = this.fenceIdx = 0; - continue - } + this.utils.decorateOpenFile(null, () => { + (this.config.AUTO_REFRESH_WHEN_OPEN_FILE && this.entities.modal.style.display === "block") && setTimeout(this.refresh, 300); + }) - const cid = ele.getAttribute("cid"); - // table - if (tagName === "FIGURE") { - this.tableIdx++; - this.collection.table.push({ - cid: cid, - type: "table", - paragraphIdx: this.paragraphIdx, - idx: this.tableIdx - }); - // fence - } else if (ele.classList.contains("md-fences")) { - this.fenceIdx++; - this.collection.fence.push({ - cid: cid, - type: "fence", - paragraphIdx: this.paragraphIdx, - idx: this.fenceIdx - }); - // image - } else if (ele.querySelector("img")) { - this.imageIdx++; - this.collection.image.push({ - cid: cid, - type: "image", - paragraphIdx: this.paragraphIdx, - idx: this.imageIdx - }); - } - } - } + this.entities.modal.addEventListener("click", ev => { + const item = ev.target.closest(".plugin-outline-item"); + const headerIcon = ev.target.closest(".plugin-outline-header .plugin-outline-icon"); + const footerIcon = ev.target.closest(".plugin-outline-footer .plugin-outline-icon"); - compare(p) { - return function (m, n) { - const cid1 = parseInt(m[p].replace("n", "")); - const cid2 = parseInt(n[p].replace("n", "")); - return cid1 - cid2; - } - } + if (!item && !headerIcon && !footerIcon) return; - getCollection(Type) { - if (Type !== "all") { - return this.collection[Type] - } - let list = []; - for (const type in this.collection) { - list.push(...this.collection[type]) - } - list.sort(this.compare("cid")); - return list - } + ev.stopPropagation(); + ev.preventDefault(); - setColor = (ele, item, type) => { - if (type === "all") { - if (item.type === "table") { - ele.style.backgroundColor = "aliceblue"; - } else if (item.type === "fence") { - ele.style.backgroundColor = "antiquewhite"; - } else if (item.type === "image") { - ele.style.backgroundColor = "beige"; - } + if (item) { + const cid = item.querySelector("span").getAttribute("data-ref"); + this.scroll(cid); + } else if (footerIcon) { + const Type = footerIcon.getAttribute("type"); + this.collectAndShow(Type); } else { - ele.style.backgroundColor = ""; - } - } - - // 简易数据单向绑定 - bindDOM(Type) { - const typeCollection = this.getCollection(Type); - - const first = entities.list.firstElementChild; - if (first && !first.classList.contains("plugin-outline-item")) { - entities.list.removeChild(first); - } - - while (typeCollection.length !== entities.list.childElementCount) { - if (typeCollection.length > entities.list.childElementCount) { - const div = document.createElement("div"); - div.classList.add("plugin-outline-item"); - div.appendChild(document.createElement("span")); - entities.list.appendChild(div); - } else { - entities.list.removeChild(entities.list.firstElementChild); + const Type = headerIcon.getAttribute("type"); + if (Type === "close") { + this.hide(); + } else if (Type === "refresh") { + this.refresh(); + this.rotate(headerIcon.firstElementChild); + } else if (Type === "eye") { + this.toggleEye(headerIcon); + this.refresh(); } } - - if (entities.list.childElementCount === 0) { - const div = document.createElement("div"); - div.innerText = "Empty"; - div.style.display = "block"; - div.style.textAlign = "center"; - div.style.padding = "10px"; - entities.list.appendChild(div); - return - } - - let ele = entities.list.firstElementChild; - typeCollection.forEach(item => { - if (config.SET_COLOR_IN_ALL) { - this.setColor(ele, item, Type); - } - const span = ele.firstElementChild; - span.setAttribute("data-ref", item.cid); - span.innerText = `${config.SHOW_NAME[item.type]} ${item.paragraphIdx}-${item.idx}`; - ele = ele.nextElementSibling; - }) - } - } - - const collectUtil = new _collectUtil(); - - const collectAndShow = Type => { - setFooterActive(Type); - collectUtil.collect(); - collectUtil.bindDOM(Type); - entities.modal.style.display = "block"; + }) } - const collapsePlugin = global._pluginUtils.getPlugin("collapse_paragraph"); - const truncatePlugin = global._pluginUtils.getPlugin("truncate_text"); - const compatibleOtherPlugin = target => { - if (!target) return; - - collapsePlugin && collapsePlugin.meta && collapsePlugin.meta.rollback && collapsePlugin.meta.rollback(target); - truncatePlugin && truncatePlugin.meta && truncatePlugin.meta.rollback && truncatePlugin.meta.rollback(target); + collectAndShow = Type => { + this.setFooterActive(Type); + this.collectUtil.collect(); + this.collectUtil.bindDOM(Type); + this.entities.modal.style.display = "block"; } - const scroll = cid => { + scroll = cid => { const target = File.editor.findElemById(cid); - compatibleOtherPlugin(target[0]); + this.utils.showHiddenElementByPlugin(target[0]); File.editor.focusAndRestorePos(); File.editor.selection.scrollAdjust(target, 10); File.isFocusMode && File.editor.updateFocusMode(false); } - const setFooterActive = Type => { - for (let ele = entities.footer.firstElementChild; !!ele; ele = ele.nextElementSibling) { + setFooterActive = Type => { + for (let ele = this.entities.footer.firstElementChild; !!ele; ele = ele.nextElementSibling) { if (ele.getAttribute("type") === Type) { ele.classList.add("select"); } else { @@ -272,15 +162,15 @@ } } - const refresh = () => { - if (entities.modal.style.display === "block") { - const search = entities.footer.querySelector(".plugin-outline-icon.select"); - collectAndShow(search.getAttribute("Type")); + refresh = () => { + if (this.entities.modal.style.display === "block") { + const search = this.entities.footer.querySelector(".plugin-outline-icon.select"); + this.collectAndShow(search.getAttribute("Type")); } } // 因为比较简单,就不用CSS做了 - const rotate = ele => { + rotate = ele => { let angle = 0; const timer = setInterval(() => { angle += 10; @@ -289,8 +179,8 @@ }, 10) } - const toggleEye = icon => { - config.SHOW_HIDDEN = !config.SHOW_HIDDEN; + toggleEye = icon => { + this.config.SHOW_HIDDEN = !this.config.SHOW_HIDDEN; if (icon.classList.contains("ion-eye")) { icon.classList.remove("ion-eye"); icon.classList.add("ion-eye-disabled"); @@ -302,59 +192,163 @@ } } - entities.modal.addEventListener("click", ev => { - const item = ev.target.closest(".plugin-outline-item"); - const headerIcon = ev.target.closest(".plugin-outline-header .plugin-outline-icon"); - const footerIcon = ev.target.closest(".plugin-outline-footer .plugin-outline-icon"); + call = () => { + if (this.entities.modal.style.display === "block") { + this.hide(); + } else { + this.collectAndShow(this.config.DEFAULT_TYPE); + } + }; - if (!item && !headerIcon && !footerIcon) return; + hide = () => this.entities.modal.style.display = "none"; +} - ev.stopPropagation(); - ev.preventDefault(); +class _collectUtil { + constructor(config, entities) { + this.config = config; + this.entities = entities; - if (item) { - const cid = item.querySelector("span").getAttribute("data-ref"); - scroll(cid); - } else if (footerIcon) { - const Type = footerIcon.getAttribute("type"); - collectAndShow(Type); - } else { - const Type = headerIcon.getAttribute("type"); - if (Type === "close") { - hide(); - } else if (Type === "refresh") { - refresh(); - rotate(headerIcon.firstElementChild); - } else if (Type === "eye") { - toggleEye(headerIcon); - refresh(); + this.paragraphIdx = 0; + this.tableIdx = 0; + this.imageIdx = 0; + this.fenceIdx = 0; + this.collection = {table: [], image: [], fence: []}; + } + + clear() { + this.paragraphIdx = this.tableIdx = this.imageIdx = this.fenceIdx = 0; + this.collection = {table: [], image: [], fence: []}; + } + + collect() { + this.clear(); + const write = document.querySelector("#write"); + for (let ele = write.firstElementChild; ele; ele = ele.nextElementSibling) { + if (!this.config.SHOW_HIDDEN && ele.style.display === "none") { + continue + } + + const tagName = ele.tagName; + if (tagName === "H1") { + this.paragraphIdx = 0; + this.tableIdx = this.imageIdx = this.fenceIdx = 0; + continue + } else if (tagName === "H2") { + this.paragraphIdx++; + this.tableIdx = this.imageIdx = this.fenceIdx = 0; + continue + } + + const cid = ele.getAttribute("cid"); + // table + if (tagName === "FIGURE") { + this.tableIdx++; + this.collection.table.push({ + cid: cid, + type: "table", + paragraphIdx: this.paragraphIdx, + idx: this.tableIdx + }); + // fence + } else if (ele.classList.contains("md-fences")) { + this.fenceIdx++; + this.collection.fence.push({ + cid: cid, + type: "fence", + paragraphIdx: this.paragraphIdx, + idx: this.fenceIdx + }); + // image + } else if (ele.querySelector("img")) { + this.imageIdx++; + this.collection.image.push({ + cid: cid, + type: "image", + paragraphIdx: this.paragraphIdx, + idx: this.imageIdx + }); } } - }) + } - global._pluginUtils.dragFixedModal(entities.move, entities.modal, false); + compare(p) { + return function (m, n) { + const cid1 = parseInt(m[p].replace("n", "")); + const cid2 = parseInt(n[p].replace("n", "")); + return cid1 - cid2; + } + } - global._pluginUtils.decorateOpenFile(null, () => { - (config.AUTO_REFRESH_WHEN_OPEN_FILE && entities.modal.style.display === "block") && setTimeout(refresh, 300); - }) + getCollection(Type) { + if (Type !== "all") { + return this.collection[Type] + } + let list = []; + for (const type in this.collection) { + list.push(...this.collection[type]) + } + list.sort(this.compare("cid")); + return list + } - const call = () => { - if (entities.modal.style.display === "block") { - hide(); + setColor = (ele, item, type) => { + if (type === "all") { + if (item.type === "table") { + ele.style.backgroundColor = "aliceblue"; + } else if (item.type === "fence") { + ele.style.backgroundColor = "antiquewhite"; + } else if (item.type === "image") { + ele.style.backgroundColor = "beige"; + } } else { - collectAndShow(config.DEFAULT_TYPE); + ele.style.backgroundColor = ""; } - }; + } - const hide = () => entities.modal.style.display = "none"; + // 简易数据单向绑定 + bindDOM(Type) { + const typeCollection = this.getCollection(Type); - module.exports = { - call, - meta: { - hide, - refresh, + const first = this.entities.list.firstElementChild; + if (first && !first.classList.contains("plugin-outline-item")) { + this.entities.list.removeChild(first); } - }; - console.log("outline.js had been injected"); -})() \ No newline at end of file + while (typeCollection.length !== this.entities.list.childElementCount) { + if (typeCollection.length > this.entities.list.childElementCount) { + const div = document.createElement("div"); + div.classList.add("plugin-outline-item"); + div.appendChild(document.createElement("span")); + this.entities.list.appendChild(div); + } else { + this.entities.list.removeChild(this.entities.list.firstElementChild); + } + } + + if (this.entities.list.childElementCount === 0) { + const div = document.createElement("div"); + div.innerText = "Empty"; + div.style.display = "block"; + div.style.textAlign = "center"; + div.style.padding = "10px"; + this.entities.list.appendChild(div); + return + } + + let ele = this.entities.list.firstElementChild; + typeCollection.forEach(item => { + if (this.config.SET_COLOR_IN_ALL) { + this.setColor(ele, item, Type); + } + const span = ele.firstElementChild; + span.setAttribute("data-ref", item.cid); + span.innerText = `${this.config.SHOW_NAME[item.type]} ${item.paragraphIdx}-${item.idx}`; + ele = ele.nextElementSibling; + }) + } +} + +module.exports = { + plugin: outlinePlugin +}; + diff --git a/plugin/read_only.js b/plugin/read_only.js index 5f404d93..da0118fc 100644 --- a/plugin/read_only.js +++ b/plugin/read_only.js @@ -1,15 +1,56 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("read_only"); +class readOnlyPlugin extends global._basePlugin { + style = () => { + const textID = "plugin-read-only-style"; + const text = `#footer-word-count-label::before {content: attr(data-value) !important}`; + return {textID, text} + } + + hotkey = () => { + return [{ + hotkey: this.config.HOTKEY, + callback: this.call, + }] + } + + init = () => { + this.excludeList = this.config.EXCLUDE_HOTKEY.map(h => this.utils.toHotkeyFunc(h)); + this.lastClickTime = 0; + } - (() => { - const css = `#footer-word-count-label::before {content: attr(data-value) !important}`; - global._pluginUtils.insertStyle("plugin-read-only-style", css); - })() + process = () => { + this.init(); - const excludeList = config.EXCLUDE_HOTKEY.map(h => global._pluginUtils.toHotkeyFunc(h)); + const write = document.getElementById("write"); + write.addEventListener("keydown", this.stopKeyboard, true); + write.addEventListener("mousedown", this.stopMouse, true); + write.addEventListener("click", this.stopMouse, true); - const isExclude = ev => { - for (const func of excludeList) { + if (this.config.READ_ONLY_DEFAULT) { + this.utils.loopDetector(() => !!File, this.call); + } + + this.utils.decorate( + () => (File && File.freshLock), + File, + "freshLock", + null, + () => { + if (!File.isLocked) return; + [ + "#typora-search-multi-input input", + "#typora-commander-form input", + "#plugin-multi-highlighter-input input", + "#typora-quick-open-input input", + ].forEach(selector => { + const input = document.querySelector(selector); + input && input.removeAttribute("readonly"); + }) + } + ) + } + + isExclude = ev => { + for (const func of this.excludeList) { if (func(ev)) { return true } @@ -17,7 +58,7 @@ return false } - const stopMouse = ev => { + stopMouse = ev => { if (!File.isLocked) return; const target = ev.target.closest('.footnotes, figure[mdtype="table"], .md-task-list-item, .md-image, .ty-cm-lang-input, input[type="checkbox"]'); @@ -28,29 +69,23 @@ } } - let lastClickTime = 0; - const stopKeyboard = ev => { + stopKeyboard = ev => { if (!File.isLocked) return; - if (ev.timeStamp - lastClickTime > config.CLICK_CHECK_INTERVAL) { + if (ev.timeStamp - this.lastClickTime > this.config.CLICK_CHECK_INTERVAL) { File.lock(); } // File.isLocked 也挡不住回车键 :( // 为什么要使用isExclude排除按键?因为输入法激活状态下键入能突破 File.isLocked - if ((ev.key === "Enter") || !isExclude(ev)) { + if ((ev.key === "Enter") || !this.isExclude(ev)) { document.activeElement.blur(); ev.preventDefault(); ev.stopPropagation(); } } - const write = document.getElementById("write"); - write.addEventListener("keydown", stopKeyboard, true); - write.addEventListener("mousedown", stopMouse, true); - write.addEventListener("click", stopMouse, true); - - const call = () => { + call = () => { const span = document.getElementById("footer-word-count-label"); if (File.isLocked) { File.unlock(); @@ -61,35 +96,8 @@ span.setAttribute("data-value", "ReadOnly" + String.fromCharCode(160).repeat(3)); } } +} - global._pluginUtils.registerWindowHotkey(config.HOTKEY, call); - - global._pluginUtils.decorate( - () => (File && File.freshLock), - File, - "freshLock", - null, - () => { - if (!File.isLocked) return; - [ - "#typora-search-multi-input input", - "#typora-commander-form input", - "#plugin-multi-highlighter-input input", - "#typora-quick-open-input input", - ].forEach(selector => { - const input = document.querySelector(selector); - input && input.removeAttribute("readonly"); - }) - } - ) - - if (config.READ_ONLY_DEFAULT) { - global._pluginUtils.loopDetector(() => !!File, call); - } - - module.exports = { - call, - }; - - console.log("read_only.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: readOnlyPlugin, +}; diff --git a/plugin/resize_image.js b/plugin/resize_image.js index 4a27b2d6..ffa36faf 100644 --- a/plugin/resize_image.js +++ b/plugin/resize_image.js @@ -1,12 +1,37 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("resize_image"); +class resizeImagePlugin extends global._basePlugin { + init = () => { + this.dynamicUtil = {target: null} + this.dynamicCallMap = { + zoom_out_20_percent: () => this.zoom(this.dynamicUtil.target, true, 0.2), + zoom_in_20_percent: () => this.zoom(this.dynamicUtil.target, false, 0.2), + set_align_left: () => this.setAlign("left", this.dynamicUtil.target), + set_align_center: () => this.setAlign("center", this.dynamicUtil.target), + set_align_right: () => this.setAlign("right", this.dynamicUtil.target), + } + } + + process = () => { + this.init(); + + document.getElementById("write").addEventListener("wheel", ev => { + if (!this.utils.metaKeyPressed(ev)) return; + + const target = ev.target.closest("img"); + if (!target) return; + + ev.stopPropagation(); + + const zoomOut = ev.deltaY > 0; + this.zoom(target, zoomOut, this.config.SCALE); + }, true); + } - const getWidth = image => { + getWidth = image => { const {width} = image.getBoundingClientRect(); return (!image.style.width) ? width : parseInt(image.style.width.replace("px", "")); } - const setAlign = (align, image, maxWidth) => { + setAlign = (align, image, maxWidth) => { image.setAttribute("align", align); if (!maxWidth) { maxWidth = image.parentElement.offsetWidth; @@ -14,46 +39,32 @@ image.style.marginRight = ""; image.style.marginLeft = ""; if (align !== "center") { - const width = getWidth(image); + const width = this.getWidth(image); const margin = (align === "left") ? "marginRight" : "marginLeft"; image.style[margin] = maxWidth - width + "px"; } } - const zoom = (image, zoomOut, scale) => { - let width = getWidth(image); - width = zoomOut ? width * (1 - scale) : width * (1 + config.SCALE); + zoom = (image, zoomOut, scale) => { + let width = this.getWidth(image); + width = zoomOut ? width * (1 - scale) : width * (1 + this.config.SCALE); const maxWidth = image.parentElement.offsetWidth; width = Math.min(width, maxWidth); image.style.width = width + "px"; - setAlign(config.IMAGE_ALIGN, image, maxWidth); + this.setAlign(this.config.IMAGE_ALIGN, image, maxWidth); } - document.getElementById("write").addEventListener("wheel", ev => { - if (!global._pluginUtils.metaKeyPressed(ev)) return; - - const target = ev.target.closest("img"); - if (!target) return; - - ev.stopPropagation(); - - const zoomOut = ev.deltaY > 0; - zoom(target, zoomOut, config.SCALE); - }, true); - - //////////////////////// 以下是声明式插件系统代码 //////////////////////// - const dynamicUtil = {target: null} - const dynamicCallArgsGenerator = anchorNode => { + dynamicCallArgsGenerator = anchorNode => { const images = anchorNode.closest("#write .md-image"); if (!images) return; const image = images.querySelector("img"); if (!image) return; - dynamicUtil.target = image; + this.dynamicUtil.target = image; const args = [{arg_name: "缩小20%", arg_value: "zoom_out_20_percent"}]; - if (getWidth(image) < image.parentElement.offsetWidth) { + if (this.getWidth(image) < image.parentElement.offsetWidth) { args.push({arg_name: "放大20%", arg_value: "zoom_in_20_percent"}) } args.push( @@ -64,25 +75,14 @@ return args } - const dynamicCallMap = { - zoom_out_20_percent: () => zoom(dynamicUtil.target, true, 0.2), - zoom_in_20_percent: () => zoom(dynamicUtil.target, false, 0.2), - set_align_left: () => setAlign("left", dynamicUtil.target), - set_align_center: () => setAlign("center", dynamicUtil.target), - set_align_right: () => setAlign("right", dynamicUtil.target), - } - - const call = type => { - if (!dynamicUtil.target) return; + call = type => { + if (!this.dynamicUtil.target) return; - const func = dynamicCallMap[type]; + const func = this.dynamicCallMap[type]; func && func(); } +} - module.exports = { - call, - dynamicCallArgsGenerator, - }; - - console.log("resize_image.js had been injected"); -})() +module.exports = { + plugin: resizeImagePlugin +}; \ No newline at end of file diff --git a/plugin/resize_table.js b/plugin/resize_table.js index 82659633..0f26fc39 100644 --- a/plugin/resize_table.js +++ b/plugin/resize_table.js @@ -1,14 +1,81 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("resize_table"); - - (() => { - if (config.REMOVE_MIX_WIDTH) { - const css = `table.md-table td { min-width: 1px !important; }`; - global._pluginUtils.insertStyle("plugin-resize-table-style", css); +class resizeTablePlugin extends global._basePlugin { + style = () => { + if (this.config.REMOVE_MIX_WIDTH) { + const textID = "plugin-resize-table-style"; + const text = `table.md-table td { min-width: 1px !important; }`; + return {textID, text} } - })() + } + + process = () => { + document.querySelector("#write").addEventListener("mousedown", ev => { + if (!this.utils.metaKeyPressed(ev)) return; + ev.stopPropagation(); + ev.preventDefault(); + + let closet = "thead"; + let self = "th"; + let ele = ev.target.closest(self); + if (!ele) { + closet = "tbody"; + self = "td"; + ele = ev.target.closest(self); + } + + if (!ele) return; + + const {target, direction} = this.findTarget(ele, ev); + if ((!target) || (direction !== "right" && direction !== "bottom")) return; + + const rect = target.getBoundingClientRect(); + const startWidth = rect.width; + const startHeight = rect.height; + const startX = ev.clientX; + const startY = ev.clientY; + + target.style.width = startWidth + "px"; + target.style.height = startHeight + "px"; + + if (direction === "right") { + target.style.cursor = "w-resize"; + const num = this.whichChildOfParent(target); + const eleList = target.closest(closet).querySelectorAll(`tr ${self}:nth-child(${num})`); + this.cleanStyle(eleList, target, "width"); + } else if (direction === "bottom") { + target.style.cursor = "s-resize"; + const tds = target.parentElement.children; + this.cleanStyle(tds, target, "height"); + } + + const onMouseMove = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + if (!this.utils.metaKeyPressed(ev)) return; + + requestAnimationFrame(() => { + if (direction === "right") { + target.style.width = startWidth + ev.clientX - startX + "px"; + } else if (direction === "bottom") { + target.style.height = startHeight + ev.clientY - startY + "px"; + } + }); + } + + document.addEventListener("mouseup", ev => { + ev.stopPropagation(); + ev.preventDefault(); + target.style.cursor = "default"; + document.removeEventListener('mousemove', onMouseMove); + target.onmouseup = null; + } + ) + + document.addEventListener('mousemove', onMouseMove); + }) + } - const whichChildOfParent = child => { + whichChildOfParent = child => { let i = 1; for (const sibling of child.parentElement.children) { if (sibling && sibling === child) { @@ -18,19 +85,19 @@ } } - const getDirection = (target, ev) => { + getDirection = (target, ev) => { if (!target) return "" const rect = target.getBoundingClientRect(); - if (rect.right - config.THRESHOLD < ev.clientX && ev.clientX < rect.right + config.THRESHOLD) { + if (rect.right - this.config.THRESHOLD < ev.clientX && ev.clientX < rect.right + this.config.THRESHOLD) { return "right" - } else if (rect.bottom - config.THRESHOLD < ev.clientY && ev.clientY < rect.bottom + config.THRESHOLD) { + } else if (rect.bottom - this.config.THRESHOLD < ev.clientY && ev.clientY < rect.bottom + this.config.THRESHOLD) { return "bottom" } else { return "" } } - const findTarget = (ele, ev) => { + findTarget = (ele, ev) => { let target = null; let direction = ""; @@ -43,7 +110,7 @@ target = ele.previousElementSibling; break case 3: - const num = whichChildOfParent(ele); + const num = this.whichChildOfParent(ele); const uncle = ele.parentElement.previousElementSibling; if (uncle) { target = uncle.querySelector(`td:nth-child(${num})`); @@ -55,88 +122,22 @@ break } - direction = getDirection(target, ev); + direction = this.getDirection(target, ev); if (target && direction) break } return {target, direction} } - const cleanStyle = (eleList, exclude, cleanStyle) => { + cleanStyle = (eleList, exclude, cleanStyle) => { for (const td of eleList) { if (td && td.style && td !== exclude) { td.style[cleanStyle] = ""; } } } +} - document.querySelector("#write").addEventListener("mousedown", ev => { - if (!global._pluginUtils.metaKeyPressed(ev)) return; - ev.stopPropagation(); - ev.preventDefault(); - - let closet = "thead"; - let self = "th"; - let ele = ev.target.closest(self); - if (!ele) { - closet = "tbody"; - self = "td"; - ele = ev.target.closest(self); - } - - if (!ele) return; - - const {target, direction} = findTarget(ele, ev); - if ((!target) || (direction !== "right" && direction !== "bottom")) return; - - const rect = target.getBoundingClientRect(); - const startWidth = rect.width; - const startHeight = rect.height; - const startX = ev.clientX; - const startY = ev.clientY; - - target.style.width = startWidth + "px"; - target.style.height = startHeight + "px"; - - if (direction === "right") { - target.style.cursor = "w-resize"; - const num = whichChildOfParent(target); - const eleList = target.closest(closet).querySelectorAll(`tr ${self}:nth-child(${num})`); - cleanStyle(eleList, target, "width"); - } else if (direction === "bottom") { - target.style.cursor = "s-resize"; - const tds = target.parentElement.children; - cleanStyle(tds, target, "height"); - } - - const onMouseMove = ev => { - ev.stopPropagation(); - ev.preventDefault(); - - if (!global._pluginUtils.metaKeyPressed(ev)) return; - - requestAnimationFrame(() => { - if (direction === "right") { - target.style.width = startWidth + ev.clientX - startX + "px"; - } else if (direction === "bottom") { - target.style.height = startHeight + ev.clientY - startY + "px"; - } - }); - } - - document.addEventListener("mouseup", ev => { - ev.stopPropagation(); - ev.preventDefault(); - target.style.cursor = "default"; - document.removeEventListener('mousemove', onMouseMove); - target.onmouseup = null; - } - ) - - document.addEventListener('mousemove', onMouseMove); - }) - - module.exports = {}; - - console.log("resize_table.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: resizeTablePlugin +}; \ No newline at end of file diff --git a/plugin/right_click_menu.js b/plugin/right_click_menu.js index fe2a5dd9..3ca5e82c 100644 --- a/plugin/right_click_menu.js +++ b/plugin/right_click_menu.js @@ -1,14 +1,20 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("right_click_menu"); +class rightClickMenuPlugin extends global._basePlugin { + beforeProcess = () => { + this.utils.loopDetector(() => global._pluginsHadInjected, this.appendMenu, this.config.LOOP_DETECT_INTERVAL); + } - const getPlugins = () => { - const enable = global._plugins.filter(plugin => plugin.enable === true); - const clickable = enable.filter(plugin => plugin.clickable === true); - const nonClickable = enable.filter(plugin => plugin.clickable === false); + getPlugins = () => { + const enable = [] + for (const fixed_name in global._plugins) { + const plugin = global._plugins[fixed_name]; + enable.push(plugin); + } + const clickable = enable.filter(plugin => plugin.config.CLICKABLE === true); + const nonClickable = enable.filter(plugin => plugin.config.CLICKABLE === false); return {clickable, nonClickable, enable} } - const appendFirst = () => { + appendFirst = () => { const ul = document.querySelector(`#context-menu`); const line = document.createElement("li"); line.classList.add("divider"); @@ -25,45 +31,45 @@ ul.insertAdjacentHTML('beforeend', li); } - const appendSecond = (clickablePlugins, nonClickablePlugins) => { - const clickable = clickablePlugins.map(plugin => createSecondLi(plugin)).join(""); - const nonClickable = nonClickablePlugins.map(plugin => createSecondLi(plugin)).join(""); + appendSecond = (clickablePlugins, nonClickablePlugins) => { + const clickable = clickablePlugins.map(plugin => this.createSecondLi(plugin)).join(""); + const nonClickable = nonClickablePlugins.map(plugin => this.createSecondLi(plugin)).join(""); const divider = `
  • ` - const secondUl = createUl(); + const secondUl = this.createUl(); secondUl.id = "plugin-menu"; secondUl.innerHTML = clickable + divider + nonClickable; document.querySelector("content").appendChild(secondUl); } - const appendThird = enablePlugins => { + appendThird = enablePlugins => { enablePlugins.forEach(plugin => { - if (!plugin.call_args && !plugin.dynamic_call_args_generator) return; + if (!plugin.callArgs && !plugin.dynamicCallArgsGenerator) return; - const thirdUl = createUl(); + const thirdUl = this.createUl(); thirdUl.classList.add("plugin-menu-third"); thirdUl.setAttribute("fixed_name", plugin.fixed_name); - thirdUl.innerHTML = plugin.call_args ? plugin.call_args.map(arg => createThirdLi(arg)).join("") : ""; + thirdUl.innerHTML = plugin.callArgs ? plugin.callArgs.map(arg => this.createThirdLi(arg)).join("") : ""; document.querySelector("content").appendChild(thirdUl); }) } - const createSecondLi = plugin => { - const hasNotArgs = !plugin.call_args && !plugin.dynamic_call_args_generator; - const style = (plugin.clickable) ? "" : `style="pointer-events: none;color: #c4c6cc;"`; - const content = (hasNotArgs) ? plugin.name : `${plugin.name} `; + createSecondLi = plugin => { + const hasNotArgs = !plugin.callArgs && !plugin.dynamicCallArgsGenerator; + const style = (plugin.config.CLICKABLE) ? "" : `style="pointer-events: none;color: #c4c6cc;"`; + const content = (hasNotArgs) ? plugin.config.NAME : `${plugin.config.NAME} `; const className = (hasNotArgs) ? "" : "plugin-has-args"; return `
  • ${content}
  • ` } - const createThirdLi = (arg, dynamic) => { + createThirdLi = (arg, dynamic) => { const disabled = (arg.arg_disabled) ? " disabled" : ""; const className = (dynamic) ? `class="plugin-dynamic-arg${disabled}"` : ""; return `
  • ${arg.arg_name}
  • ` } - const createUl = () => { + createUl = () => { const secondUl = document.createElement("ul"); secondUl.classList.add("dropdown-menu"); secondUl.classList.add("context-menu"); @@ -72,7 +78,7 @@ return secondUl; } - const show = (second, first) => { + show = (second, first) => { const next = second.addClass("show"); const rect = next[0].getBoundingClientRect(); @@ -94,38 +100,57 @@ return false; } - const generateDynamicCallArgs = fixedName => { + generateDynamicCallArgs = fixedName => { if (!fixedName) return; - const plugin = global._pluginUtils.getPlugin(fixedName); - if (plugin && plugin.enable && plugin.dynamic_call_args_generator) { + const plugin = this.utils.getPlugin(fixedName); + if (plugin && plugin.dynamicCallArgsGenerator) { const anchorNode = File.editor.getJQueryElem(window.getSelection().anchorNode); if (anchorNode[0]) { - return plugin.dynamic_call_args_generator(anchorNode[0]); + return plugin.dynamicCallArgsGenerator(anchorNode[0]); } } } - const appendThirdLi = (menu, dynamicCallArgs) => { - const args = dynamicCallArgs.map(arg => createThirdLi(arg, true)).join(""); + appendThirdLi = (menu, dynamicCallArgs) => { + const args = dynamicCallArgs.map(arg => this.createThirdLi(arg, true)).join(""); menu.append(args); } - - const appendDummyThirdLi = menu => { - appendThirdLi(menu, [{ + appendDummyThirdLi = menu => { + this.appendThirdLi(menu, [{ arg_name: "光标于此位置不可用", - arg_value: config.NOT_AVAILABLE_VALUE, + arg_value: this.config.NOT_AVAILABLE_VALUE, arg_disabled: true, }]) } - const listen = enablePlugins => { + appendMenu = () => { + setTimeout(() => { + const {clickable, nonClickable, enable} = this.getPlugins(); + // 一级菜单汇总所有插件 + this.appendFirst(); + // 二级菜单展示所有插件 + this.appendSecond(clickable, nonClickable); + // 三级菜单展示插件的参数 + this.appendThird(enable); + this.listen(); + }, 500) + } + + listen = () => { + const appendThirdLi = this.appendThirdLi; + const appendDummyThirdLi = this.appendDummyThirdLi; + const show = this.show; + const generateDynamicCallArgs = this.generateDynamicCallArgs; + const config = this.config; + const utils = this.utils + // 在二级菜单中调用插件 $("#plugin-menu").on("click", "[data-key]", function () { - const fixed_name = this.getAttribute("data-key"); - const plugin = enablePlugins.filter(plugin => plugin.fixed_name === fixed_name)[0]; + const fixedName = this.getAttribute("data-key"); + const plugin = utils.getPlugin(fixedName); // 拥有三级菜单的,不允许点击二级菜单 - if (plugin.call_args || plugin.dynamic_call_args_generator) { + if (plugin.callArgs || plugin.dynamicCallArgsGenerator) { return false } if (plugin && plugin.call) { @@ -170,7 +195,7 @@ $(".plugin-menu-third").on("click", "[data-key]", function () { const fixedName = this.parentElement.getAttribute("fixed_name"); const argValue = this.getAttribute("arg_value"); - const plugin = enablePlugins.filter(plugin => plugin.fixed_name === fixedName)[0]; + const plugin = utils.getPlugin(fixedName); if (argValue !== config.NOT_AVAILABLE_VALUE && plugin && plugin.call) { plugin.call(argValue); } @@ -180,36 +205,20 @@ }) } - const appendMenu = () => { - setTimeout(() => { - const {clickable, nonClickable, enable} = getPlugins(); - // 一级菜单汇总所有插件 - appendFirst(); - // 二级菜单展示所有插件 - appendSecond(clickable, nonClickable); - // 三级菜单展示插件的参数 - appendThird(enable); - listen(enable); - }, 500) - } - - global._pluginUtils.loopDetector(() => global._pluginsHadInjected, appendMenu, config.LOOP_DETECT_INTERVAL); - - //////////////////////// 以下是声明式插件系统代码 //////////////////////// - const call = type => { + call = type => { if (type === "about") { const url = "https://github.com/obgnail/typora_plugin" const openUrl = File.editor.tryOpenUrl_ || File.editor.tryOpenUrl openUrl(url, 1); } else if (type === "do_not_hide") { - config.DO_NOT_HIDE = !config.DO_NOT_HIDE; + this.config.DO_NOT_HIDE = !this.config.DO_NOT_HIDE; } else if (type === "open_setting_folder") { - const filepath = global._pluginUtils.joinPath("./plugin/global/settings/settings.toml"); + const filepath = this.utils.joinPath("./plugin/global/settings/settings.toml"); JSBridge.showInFinder(filepath); } } - const callArgs = [ + callArgs = [ { arg_name: "右键菜单点击后保持显示/隐藏", arg_value: "do_not_hide" @@ -222,12 +231,9 @@ arg_name: "关于/帮助", arg_value: "about" }, - ]; - - module.exports = { - call, - callArgs, - }; + ] +} - console.log("right_click_menu.js had been injected"); -})() \ No newline at end of file +module.exports = { + plugin: rightClickMenuPlugin +}; diff --git a/plugin/search_multi.js b/plugin/search_multi.js index 03df379b..5cdc8cac 100644 --- a/plugin/search_multi.js +++ b/plugin/search_multi.js @@ -1,8 +1,7 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("search_multi"); - - (() => { - const modal_css = ` +class searchMultiKeywordPlugin extends global._basePlugin { + style = () => { + const textID = "plugin-search-multi-style"; + const text = ` #typora-search-multi { position: fixed; top: 40px; @@ -143,17 +142,19 @@ padding-left: 20px; display: none; }` - global._pluginUtils.insertStyle("plugin-search-multi-style", modal_css); + return {textID, text} + } + html = () => { const modal_div = `
    - + - +
    @@ -179,55 +180,186 @@ searchModal.innerHTML = modal_div; const quickOpenNode = document.getElementById("typora-quick-open"); quickOpenNode.parentNode.insertBefore(searchModal, quickOpenNode.nextSibling); - })(); - - const modal = { - modal: document.getElementById('typora-search-multi'), - input: document.querySelector("#typora-search-multi-input input"), - result: document.querySelector(".typora-search-multi-result"), - resultTitle: document.querySelector(".typora-search-multi-result .search-result-title"), - resultList: document.querySelector(".typora-search-multi-result .search-result-list"), - info: document.querySelector(".typora-search-multi-info-item"), } - const Package = global._pluginUtils.Package; - const separator = File.isWin ? "\\" : "/"; - const openFileInThisWindow = filePath => File.editor.library.openFile(filePath); - const openFileInNewWindow = (path, isFolder) => File.editor.library.openFileInNewWindow(path, isFolder); + hotkey = () => { + return [{ + hotkey: this.config.HOTKEY, + callback: this.call + }] + } + + process = () => { + this.modal = { + modal: document.getElementById('typora-search-multi'), + input: document.querySelector("#typora-search-multi-input input"), + result: document.querySelector(".typora-search-multi-result"), + resultTitle: document.querySelector(".typora-search-multi-result .search-result-title"), + resultList: document.querySelector(".typora-search-multi-result .search-result-list"), + info: document.querySelector(".typora-search-multi-info-item"), + } + + if (this.config.REFOUCE_WHEN_OPEN_FILE) { + this.utils.decorateOpenFile(null, () => { + if (this.modal.modal.style.display === "block") { + setTimeout(() => this.modal.input.select(), 300); + } + }) + } + + if (this.config.ALLOW_DRAG) { + this.utils.dragFixedModal(this.modal.input, this.modal.modal); + } + + let floor; + + this.modal.input.addEventListener("keydown", ev => { + switch (ev.key) { + case "Enter": + if (this.utils.metaKeyPressed(ev)) { + const select = this.modal.resultList.querySelector(".typora-search-multi-item.active"); + if (select) { + ev.preventDefault(); + ev.stopPropagation(); + const filepath = select.getAttribute("data-path"); + if (ev.shiftKey) { + this.openFileInNewWindow(filepath, false); + } else { + this.openFileInThisWindow(filepath); + } + this.modal.input.focus(); + return + } + } + this.modal.result.style.display = "none"; + this.modal.info.style.display = "block"; + this.modal.resultList.innerHTML = ""; + const workspace = File.getMountFolder(); + this.searchMulti(workspace, this.modal.input.value, () => this.modal.info.style.display = "none"); + break + case "Escape": + ev.stopPropagation(); + ev.preventDefault(); + this.hide(); + break + case "ArrowUp": + case "ArrowDown": + ev.stopPropagation(); + ev.preventDefault(); + + if (!this.modal.resultList.childElementCount) return; + + const activeItem = this.modal.resultList.querySelector(".typora-search-multi-item.active") + let nextItem; + if (ev.key === "ArrowDown") { + if (floor !== 7) floor++; + + if (activeItem && activeItem.nextElementSibling) { + nextItem = activeItem.nextElementSibling; + } else { + nextItem = this.modal.resultList.firstElementChild; + floor = 1 + } + } else { + if (floor !== 1) floor--; + + if (activeItem && activeItem.previousElementSibling) { + nextItem = activeItem.previousElementSibling; + } else { + nextItem = this.modal.resultList.lastElementChild; + floor = 7 + } + } + + activeItem && activeItem.classList.toggle("active"); + nextItem.classList.toggle("active"); + + let top; + if (floor === 1) { + top = nextItem.offsetTop - nextItem.offsetHeight; + } else if (floor === 7) { + top = nextItem.offsetTop - 6 * nextItem.offsetHeight; + } else if (Math.abs(this.modal.resultList.scrollTop - activeItem.offsetTop) > 7 * nextItem.offsetHeight) { + top = nextItem.offsetTop - 3 * nextItem.offsetHeight; + } + top && this.modal.resultList.scrollTo({top: top, behavior: "smooth"}); + } + }); + + this.modal.resultList.addEventListener("click", ev => { + const target = ev.target.closest(".typora-search-multi-item"); + if (!target) return; + + ev.preventDefault(); + ev.stopPropagation(); + + const filepath = target.getAttribute("data-path"); + if (this.utils.metaKeyPressed(ev)) { + this.openFileInNewWindow(filepath, false); + } else { + this.openFileInThisWindow(filepath); + } + this.hideIfNeed(); + }); + + this.modal.modal.addEventListener("click", ev => { + const caseButton = ev.target.closest("#typora-search-multi-input .case-option-btn"); + const pathButton = ev.target.closest("#typora-search-multi-input .path-option-btn"); + + if (caseButton || pathButton) { + ev.preventDefault(); + ev.stopPropagation(); + } + + if (caseButton) { + caseButton.classList.toggle("select"); + this.config.CASE_SENSITIVE = !this.config.CASE_SENSITIVE; + } else if (pathButton) { + pathButton.classList.toggle("select"); + this.config.INCLUDE_FILE_PATH = !this.config.INCLUDE_FILE_PATH; + } + }) + } + + separator = File.isWin ? "\\" : "/"; + openFileInThisWindow = filePath => File.editor.library.openFile(filePath); + openFileInNewWindow = (path, isFolder) => File.editor.library.openFileInNewWindow(path, isFolder); + + traverseDir = (dir, filter, callback, then) => { + const utils = this.utils; - const traverseDir = (dir, filter, callback, then) => { async function traverse(dir) { - const files = await Package.Fs.promises.readdir(dir); + const files = await utils.Package.Fs.promises.readdir(dir); for (const file of files) { - const filePath = Package.Path.join(dir, file); - const stats = await Package.Fs.promises.stat(filePath); + const filePath = utils.Package.Path.join(dir, file); + const stats = await utils.Package.Fs.promises.stat(filePath); if (stats.isFile()) { if (filter && !filter(filePath, stats)) { continue } - Package.Fs.promises.readFile(filePath) + utils.Package.Fs.promises.readFile(filePath) .then(buffer => callback(filePath, stats, buffer)) - .catch(error => console.log(error)) + .catch(error => console.error(error)) } else if (stats.isDirectory()) { await traverse(filePath); } } } - traverse(dir).then(then).catch(err => console.log(err)); + traverse(dir).then(then).catch(err => console.error(err)); } - const appendItemFunc = keyArr => { + appendItemFunc = keyArr => { let index = 0; let once = true; const rootPath = File.getMountFolder(); return (filePath, stats, buffer) => { let data = buffer.toString(); - if (config.INCLUDE_FILE_PATH) { + if (this.config.INCLUDE_FILE_PATH) { data = data + filePath; } - if (!config.CASE_SENSITIVE) { + if (!this.config.CASE_SENSITIVE) { data = data.toLowerCase(); } for (const keyword of keyArr) { @@ -235,15 +367,15 @@ } index++; - const parseUrl = Package.Path.parse(filePath); - const dirPath = !config.RELATIVE_PATH ? parseUrl.dir : parseUrl.dir.replace(rootPath, "."); + const parseUrl = this.utils.Package.Path.parse(filePath); + const dirPath = !this.config.RELATIVE_PATH ? parseUrl.dir : parseUrl.dir.replace(rootPath, "."); const item = document.createElement("div"); item.classList.add("typora-search-multi-item"); item.setAttribute("data-is-dir", "false"); item.setAttribute("data-path", filePath); item.setAttribute("data-index", index + ""); - if (config.SHOW_MTIME) { + if (this.config.SHOW_MTIME) { item.setAttribute("ty-hint", stats.mtime.toLocaleString('chinese', {hour12: false})); } const title = document.createElement("div"); @@ -251,191 +383,72 @@ title.innerText = parseUrl.base; const path = document.createElement("div"); path.classList.add("typora-search-multi-item-path"); - path.innerText = dirPath + separator; + path.innerText = dirPath + this.separator; item.appendChild(title); item.appendChild(path); - modal.resultList.appendChild(item); + this.modal.resultList.appendChild(item); - modal.resultTitle.textContent = `匹配的文件:${index}`; + this.modal.resultTitle.textContent = `匹配的文件:${index}`; if (index <= 8) { - modal.resultList.style.height = 40 * index + "px"; + this.modal.resultList.style.height = 40 * index + "px"; } if (once) { - modal.result.style.display = "block"; + this.modal.result.style.display = "block"; once = false; } } } - const hideIfNeed = () => { - if (config.AUTO_HIDE) { - modal.modal.style.display = "none"; + hideIfNeed = () => { + if (this.config.AUTO_HIDE) { + this.modal.modal.style.display = "none"; } } - const verifyExt = (filename) => { + verifyExt = filename => { if (filename[0] === ".") { return false } - const ext = Package.Path.extname(filename).replace(/^\./, ''); - if (~config.ALLOW_EXT.indexOf(ext.toLowerCase())) { + const ext = this.utils.Package.Path.extname(filename).replace(/^\./, ''); + if (~this.config.ALLOW_EXT.indexOf(ext.toLowerCase())) { return true } } - const verifySize = (stat) => 0 > config.MAX_SIZE || stat.size < config.MAX_SIZE; - const allowRead = (filepath, stat) => verifySize(stat) && verifyExt(filepath); - const searchMulti = (rootPath, keys, then) => { + verifySize = stat => 0 > this.config.MAX_SIZE || stat.size < this.config.MAX_SIZE; + + allowRead = (filepath, stat) => { + return this.verifySize(stat) && this.verifyExt(filepath); + } + + searchMulti = (rootPath, keys, then) => { if (!rootPath) return; - let keyArr = keys.split(config.SEPARATOR).filter(Boolean); + let keyArr = keys.split(this.config.SEPARATOR).filter(Boolean); if (!keyArr) return; - if (!config.CASE_SENSITIVE) { + if (!this.config.CASE_SENSITIVE) { keyArr = keyArr.map(ele => ele.toLowerCase()); } - const appendItem = appendItemFunc(keyArr); - traverseDir(rootPath, allowRead, appendItem, then); + const appendItem = this.appendItemFunc(keyArr); + this.traverseDir(rootPath, this.allowRead, appendItem, then); } - if (config.ALLOW_DRAG) { - global._pluginUtils.dragFixedModal(modal.input, modal.modal); + hide = () => { + this.modal.modal.style.display = "none"; + this.modal.info.style.display = "none"; } - let floor; - - modal.input.addEventListener("keydown", ev => { - switch (ev.key) { - case "Enter": - if (global._pluginUtils.metaKeyPressed(ev)) { - const select = modal.resultList.querySelector(".typora-search-multi-item.active"); - if (select) { - ev.preventDefault(); - ev.stopPropagation(); - const filepath = select.getAttribute("data-path"); - if (ev.shiftKey) { - openFileInNewWindow(filepath, false); - } else { - openFileInThisWindow(filepath); - } - modal.input.focus(); - return - } - } - modal.result.style.display = "none"; - modal.info.style.display = "block"; - modal.resultList.innerHTML = ""; - const workspace = File.getMountFolder(); - searchMulti(workspace, modal.input.value, () => modal.info.style.display = "none"); - break - case "Escape": - ev.stopPropagation(); - ev.preventDefault(); - hide(); - break - case "ArrowUp": - case "ArrowDown": - ev.stopPropagation(); - ev.preventDefault(); - - if (!modal.resultList.childElementCount) return; - - const activeItem = modal.resultList.querySelector(".typora-search-multi-item.active") - let nextItem; - if (ev.key === "ArrowDown") { - if (floor !== 7) floor++; - - if (activeItem && activeItem.nextElementSibling) { - nextItem = activeItem.nextElementSibling; - } else { - nextItem = modal.resultList.firstElementChild; - floor = 1 - } - } else { - if (floor !== 1) floor--; - - if (activeItem && activeItem.previousElementSibling) { - nextItem = activeItem.previousElementSibling; - } else { - nextItem = modal.resultList.lastElementChild; - floor = 7 - } - } - - activeItem && activeItem.classList.toggle("active"); - nextItem.classList.toggle("active"); - - let top; - if (floor === 1) { - top = nextItem.offsetTop - nextItem.offsetHeight; - } else if (floor === 7) { - top = nextItem.offsetTop - 6 * nextItem.offsetHeight; - } else if (Math.abs(modal.resultList.scrollTop - activeItem.offsetTop) > 7 * nextItem.offsetHeight) { - top = nextItem.offsetTop - 3 * nextItem.offsetHeight; - } - top && modal.resultList.scrollTo({top: top, behavior: "smooth"}); - } - }); - - modal.resultList.addEventListener("click", ev => { - const target = ev.target.closest(".typora-search-multi-item"); - if (!target) return; - - ev.preventDefault(); - ev.stopPropagation(); - - const filepath = target.getAttribute("data-path"); - if (global._pluginUtils.metaKeyPressed(ev)) { - openFileInNewWindow(filepath, false); + call = () => { + if (this.modal.modal.style.display === "block") { + this.hide(); } else { - openFileInThisWindow(filepath); + this.modal.modal.style.display = "block"; + this.modal.input.select(); } - hideIfNeed(); - }); - - const hide = () => { - modal.modal.style.display = "none"; - modal.info.style.display = "none"; } +} - const call = () => { - if (modal.modal.style.display === "block") { - hide(); - } else { - modal.modal.style.display = "block"; - modal.input.select(); - } - } - - global._pluginUtils.registerWindowHotkey(config.HOTKEY, call); - - modal.modal.addEventListener("click", ev => { - const caseButton = ev.target.closest("#typora-search-multi-input .case-option-btn"); - const pathButton = ev.target.closest("#typora-search-multi-input .path-option-btn"); - - if (caseButton || pathButton) { - ev.preventDefault(); - ev.stopPropagation(); - } - - if (caseButton) { - caseButton.classList.toggle("select"); - config.CASE_SENSITIVE = !config.CASE_SENSITIVE; - } else if (pathButton) { - pathButton.classList.toggle("select"); - config.INCLUDE_FILE_PATH = !config.INCLUDE_FILE_PATH; - } - }) - - if (config.REFOUCE_WHEN_OPEN_FILE) { - global._pluginUtils.decorateOpenFile(null, () => { - if (modal.modal.style.display === "block") { - setTimeout(() => modal.input.select(), 300); - } - }) - } - - module.exports = {call}; - - console.log("search_multi.js had been injected"); -})(); \ No newline at end of file +module.exports = { + plugin: searchMultiKeywordPlugin +}; diff --git a/plugin/test.js b/plugin/test.js index c3f47367..205f890d 100644 --- a/plugin/test.js +++ b/plugin/test.js @@ -1,12 +1,17 @@ -(() => { - // 打开新窗口后自动关闭 - global._pluginUtils.decorate( - () => (File && File.editor && File.editor.library && File.editor.library.openFileInNewWindow), - File.editor.library, - "openFileInNewWindow", - null, - () => (!global._DO_NOT_CLOSE) && setTimeout(() => ClientCommand.close(), 3000) - ) - JSBridge.invoke("window.toggleDevTools"); - console.log("test.js had been injected"); -})() \ No newline at end of file +class testPlugin extends global._basePlugin { + process() { + this.utils.decorate( + () => (File && File.editor && File.editor.library && File.editor.library.openFileInNewWindow), + File.editor.library, + "openFileInNewWindow", + null, + () => (!global._DO_NOT_CLOSE) && setTimeout(() => ClientCommand.close(), 3000) + ) + + JSBridge.invoke("window.toggleDevTools"); + } +} + +module.exports = { + plugin: testPlugin +}; diff --git a/plugin/truncate_text.js b/plugin/truncate_text.js index 021ed0d8..02e39839 100644 --- a/plugin/truncate_text.js +++ b/plugin/truncate_text.js @@ -1,12 +1,10 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("truncate_text"); - - const callbackOtherPlugin = () => { - const outlinePlugin = global._pluginUtils.getPlugin("outline"); - outlinePlugin && outlinePlugin.meta.refresh(); +class truncateTextPlugin extends global._basePlugin { + callbackOtherPlugin = () => { + const outlinePlugin = this.utils.getPlugin("outline"); + outlinePlugin && outlinePlugin.refresh(); } - const isInViewBox = el => { + isInViewBox = el => { if (el.style.display) return false; const totalHeight = window.innerHeight || document.documentElement.clientHeight; const totalWidth = window.innerWidth || document.documentElement.clientWidth; @@ -14,84 +12,84 @@ return (top >= 0 && left >= 0 && right <= totalWidth && bottom <= totalHeight); } - const hideFront = () => { + hideFront = () => { const write = document.getElementById("write"); const length = write.children.length; - if (length > config.REMAIN_LENGTH) { - for (let i = 0; i <= length - config.REMAIN_LENGTH; i++) { + if (length > this.config.REMAIN_LENGTH) { + for (let i = 0; i <= length - this.config.REMAIN_LENGTH; i++) { const ele = write.children[i]; - ele.classList.add(config.CLASS_NAME); + ele.classList.add(this.config.CLASS_NAME); ele.style.display = "none"; } } } - const showAll = () => { + showAll = () => { const write = document.getElementById("write"); - write.getElementsByClassName(config.CLASS_NAME).forEach(el => el.classList.remove(config.CLASS_NAME)); + write.getElementsByClassName(this.config.CLASS_NAME).forEach(el => el.classList.remove(this.config.CLASS_NAME)); write.children.forEach(el => el.style.display = ""); }; - const hideBaseView = () => { + hideBaseView = () => { const write = document.getElementById("write"); let start = 0, end = 0; write.children.forEach((ele, idx) => { - if (isInViewBox(ele)) { + if (this.isInViewBox(ele)) { if (!start) start = idx; start = Math.min(start, idx); end = Math.max(end, idx); } }); - const halfLength = config.REMAIN_LENGTH / 2; + const halfLength = this.config.REMAIN_LENGTH / 2; start = Math.max(start - halfLength, 0); end = Math.min(end + halfLength, write.children.length); write.children.forEach((ele, idx) => { if (idx < start || idx > end) { - ele.classList.add(config.CLASS_NAME); + ele.classList.add(this.config.CLASS_NAME); ele.style.display = "none"; } else { - ele.classList.remove(config.CLASS_NAME); + ele.classList.remove(this.config.CLASS_NAME); ele.style.display = ""; } }); } - // 已废弃 - const rollback2 = start => { - if (document.querySelector(`#write > .${config.CLASS_NAME}`)) { - let ele = start.closest("#write > [cid]"); - while (ele) { - if (ele.classList.contains(config.CLASS_NAME)) { - ele.classList.remove(config.CLASS_NAME); - ele.style.display = ""; - } - ele = ele.nextElementSibling; - } - } - } - - const rollback = () => { - if (document.querySelector(`#write > .${config.CLASS_NAME}`)) { - showAll(); + rollback = () => { + if (document.querySelector(`#write > .${this.config.CLASS_NAME}`)) { + this.showAll(); } }; - const call = type => { + // // 已废弃 + // rollback2 = start => { + // if (document.querySelector(`#write > .${this.config.CLASS_NAME}`)) { + // let ele = start.closest("#write > [cid]"); + // while (ele) { + // if (ele.classList.contains(this.config.CLASS_NAME)) { + // ele.classList.remove(this.config.CLASS_NAME); + // ele.style.display = ""; + // } + // ele = ele.nextElementSibling; + // } + // } + // } + + call = type => { if (type === "hide_front") { - hideFront(); + this.hideFront(); } else if (type === "show_all") { - showAll(); + this.showAll(); } else if (type === "hide_base_view") { - hideBaseView(); + this.hideBaseView(); } - callbackOtherPlugin(); + this.callbackOtherPlugin(); } - const callArgs = [ + callArgs = [ { - arg_name: `只保留最后${config.REMAIN_LENGTH}段`, + arg_name: `只保留最后${this.config.REMAIN_LENGTH}段`, arg_value: "hide_front" }, { @@ -103,13 +101,8 @@ arg_value: "hide_base_view" } ]; +} - module.exports = { - call, - callArgs, - meta: { - rollback, - } - }; - console.log("truncate_text.js had been injected"); -})() +module.exports = { + plugin: truncateTextPlugin +}; \ No newline at end of file diff --git a/plugin/window_tab/window_tab.js b/plugin/window_tab/index.js similarity index 64% rename from plugin/window_tab/window_tab.js rename to plugin/window_tab/index.js index 731d694f..feb6ea9b 100644 --- a/plugin/window_tab/window_tab.js +++ b/plugin/window_tab/index.js @@ -1,13 +1,14 @@ -(() => { - const config = global._pluginUtils.getPluginSetting("window_tab"); - - if (window._options.framelessWindow && config.HIDE_WINDOW_TITLE_BAR) { - document.querySelector("header").style.zIndex = "897"; - document.getElementById("top-titlebar").style.display = "none"; +class windowTabBarPlugin extends global._basePlugin { + beforeProcess = () => { + if (window._options.framelessWindow && this.config.HIDE_WINDOW_TITLE_BAR) { + document.querySelector("header").style.zIndex = "897"; + document.getElementById("top-titlebar").style.display = "none"; + } } - (() => { - const css = ` + style = () => { + const textID = "plugin-window-tab-style" + const text = ` #plugin-window-tab { position: fixed; top: 0; @@ -15,7 +16,7 @@ height: 40px; z-index: 898; } - + #plugin-window-tab .tab-bar { background-color: var(--bg-color, white); height: 100%; @@ -25,28 +26,28 @@ width: calc(100vw - var(--sidebar-width, 0)); overflow-x: scroll } - + #plugin-window-tab .tab-bar::after { content: ""; height: 100%; width: 100vw; border-bottom: solid 1px rgba(0, 0, 0, 0.07); } - + #plugin-window-tab .tab-bar:hover::-webkit-scrollbar-thumb { visibility: visible; } - + #plugin-window-tab .tab-bar::-webkit-scrollbar { height: 5px } - + #plugin-window-tab .tab-bar::-webkit-scrollbar-thumb { height: 5px; background-color: var(----active-file-bg-color, gray); visibility: hidden } - + #plugin-window-tab .tab-container { background-color: var(--side-bar-bg-color, gray); height: 100%; @@ -62,11 +63,11 @@ flex-shrink: 0; cursor: pointer } - + #plugin-window-tab .tab-container.over { background-color: var(--active-file-bg-color, lightgray); } - + #plugin-window-tab .name { max-width: 350px; padding-right: 15px; @@ -76,7 +77,7 @@ text-overflow: ellipsis; pointer-events: none } - + #plugin-window-tab .close-button { padding: 4px; display: flex; @@ -84,11 +85,11 @@ justify-content: center; border-radius: 5px } - + #plugin-window-tab .tab-container:hover > .close-button { visibility: visible !important } - + #plugin-window-tab .close-icon { position: relative; width: 11px; @@ -97,7 +98,7 @@ flex-direction: column; justify-content: center; } - + #plugin-window-tab .close-icon::before, #plugin-window-tab .close-icon::after { content: ""; @@ -106,29 +107,29 @@ height: 2px; background-color: var(--active-file-border-color, black) } - + #plugin-window-tab .close-icon::before { transform: rotate(45deg) } - + #plugin-window-tab .close-icon::after { transform: rotate(-45deg) } - + #plugin-window-tab .close-button:hover { background-color: var(--active-file-bg-color, lightgray); } - + #plugin-window-tab .active { border: solid 1px rgba(0, 0, 0, 0.07); border-bottom: none; background-color: var(--bg-color, white) } - + #plugin-window-tab .active .active-indicator { display: block; } - + #plugin-window-tab .active-indicator { position: absolute; top: -1px; @@ -138,13 +139,13 @@ background-color: var(--active-file-border-color, black); display: none; } - + #plugin-window-tab [dragging] { position: static !important; box-sizing: border-box !important; margin: 0 !important; } - + #plugin-window-tab .drag-obj { position: fixed; left: 0; @@ -153,8 +154,10 @@ pointer-events: none; } ` - global._pluginUtils.insertStyle("plugin-window-tab-style", css); + return {textID, text} + } + html = () => { const div = `
    ` const windowTab = document.createElement("div"); windowTab.id = "plugin-window-tab"; @@ -162,46 +165,135 @@ document.getElementById("write-style").parentElement .insertBefore(windowTab, document.getElementById("write-style")); - if (config.CHANGE_CONTENT_TOP) { + if (this.config.CHANGE_CONTENT_TOP) { const {height} = document.querySelector("#plugin-window-tab").getBoundingClientRect(); document.querySelector("content").style.top = height + "px"; } - if (config.CHANGE_NOTIFICATION_Z_INDEX) { + if (this.config.CHANGE_NOTIFICATION_Z_INDEX) { const container = document.querySelector(".md-notification-container"); if (container) { container.style.zIndex = "99999"; } } - })() + } - const Package = global._pluginUtils.Package; + hotkey = () => { + return [ + { + hotkey: this.config.SWITCH_NEXT_TAB_HOTKEY, + callback: this.nextTab + }, + { + hotkey: this.config.SWITCH_PREVIOUS_TAB_HOTKEY, + callback: this.previousTab + }, + { + hotkey: this.config.CLOSE_HOTKEY, + callback: this.closeActiveTab + }, + ] + } - const entities = { - content: document.querySelector("content"), - tabBar: document.querySelector("#plugin-window-tab .tab-bar"), + init = () => { + this.entities = { + content: document.querySelector("content"), + tabBar: document.querySelector("#plugin-window-tab .tab-bar"), + } + this.tabUtil = {tabs: [], activeIdx: 0,} + this.callMap = { + new_tab_open: () => this.config.LOCAL_OPEN = false, + local_open: () => this.config.LOCAL_OPEN = true, + save_tabs: this.saveTabs, + open_save_tabs: this.openSaveTabs, + } } - const tabUtil = { - tabs: [], - activeIdx: 0, + process = () => { + this.init(); + + this.utils.decorateOpenFile(null, (result, ...args) => { + const filePath = args[0]; + filePath && this.openTab(filePath); + }) + + this.utils.loopDetector(() => !!File, () => { + const filePath = this.utils.getFilePath(); + filePath && this.openTab(filePath); + }); + + if (this.config.DRAG_STYLE === 1) { + this.sort1(); + } else { + this.sort2(); + } + + this.entities.tabBar.addEventListener("click", ev => { + const closeButton = ev.target.closest(".close-button"); + const tabContainer = ev.target.closest(".tab-container"); + if (!closeButton && !tabContainer) return; + + ev.stopPropagation(); + ev.preventDefault(); + + const tab = closeButton ? closeButton.closest(".tab-container") : tabContainer; + const idx = parseInt(tab.getAttribute("idx")); + + if (this.utils.metaKeyPressed(ev)) { + this.openFileNewWindow(this.tabUtil.tabs[idx].path, false); + } else if (closeButton) { + this.closeTab(idx); + } else { + this.switchTab(idx); + } + }) + + + this.entities.tabBar.addEventListener("wheel", ev => { + const target = ev.target.closest("#plugin-window-tab .tab-bar"); + if (!target) return; + + if (this.utils.metaKeyPressed(ev)) { + (ev.deltaY < 0) ? this.previousTab() : this.nextTab(); + } else { + target.scrollLeft += ev.deltaY; + } + }) + + this.entities.content.addEventListener("scroll", () => { + this.tabUtil.tabs[this.tabUtil.activeIdx].scrollTop = this.entities.content.scrollTop; + }) + + document.querySelector(".typora-quick-open-list").addEventListener("mousedown", ev => { + const target = ev.target.closest(".typora-quick-open-item"); + if (!target) return; + + // 将原先的click行为改成ctrl+click + if (this.utils.metaKeyPressed(ev)) return; + + ev.preventDefault(); + ev.stopPropagation(); + const filePath = target.getAttribute("data-path"); + this.openFile(filePath); + }, true) } + // 新窗口打开 - const openFileNewWindow = (path, isFolder) => File.editor.library.openFileInNewWindow(path, isFolder) + openFileNewWindow = (path, isFolder) => File.editor.library.openFileInNewWindow(path, isFolder) // 新标签页打开 - const openFile = filePath => File.editor.library.openFile(filePath); + openFile = filePath => File.editor.library.openFile(filePath); // 当前标签页打开 - const OpenFileLocal = filePath => { - config.LOCAL_OPEN = true; + OpenFileLocal = filePath => { + this.config.LOCAL_OPEN = true; File.editor.library.openFile(filePath); - config.LOCAL_OPEN = false; // 自动还原 + this.config.LOCAL_OPEN = false; // 自动还原 } // 关闭窗口 - const closeWindow = () => JSBridge.invoke("window.close"); + closeWindow = () => JSBridge.invoke("window.close"); - const getName = filePath => { - let fileName = Package.Path.basename(filePath); + getName = filePath => { + let fileName = this.utils.Package.Path.basename(filePath); const idx = fileName.lastIndexOf("."); if (idx !== -1) { fileName = fileName.substring(0, idx); @@ -209,8 +301,8 @@ return fileName } - const newTabDiv = (filePath, idx, active = true) => { - const fileName = getName(filePath); + newTabDiv = (filePath, idx, active = true) => { + const fileName = this.getName(filePath); const _active = active ? "active" : ""; return `
    @@ -220,23 +312,23 @@ } // tabs->DOM的简单数据单向绑定 - const renderDOM = wantOpenPath => { - let tabDiv = entities.tabBar.firstElementChild; - tabUtil.tabs.forEach((tab, idx) => { + renderDOM = wantOpenPath => { + let tabDiv = this.entities.tabBar.firstElementChild; + this.tabUtil.tabs.forEach((tab, idx) => { if (!tabDiv) { - const _tabDiv = newTabDiv(tab.path, idx); - entities.tabBar.insertAdjacentHTML('beforeend', _tabDiv); - tabDiv = entities.tabBar.lastElementChild; + const _tabDiv = this.newTabDiv(tab.path, idx); + this.entities.tabBar.insertAdjacentHTML('beforeend', _tabDiv); + tabDiv = this.entities.tabBar.lastElementChild; } if (tab.path === wantOpenPath) { tabDiv.classList.add("active"); tabDiv.scrollIntoViewIfNeeded(); - scrollContent(tab); + this.scrollContent(tab); } else { tabDiv.classList.remove("active"); } tabDiv.setAttribute("idx", idx + ""); - tabDiv.querySelector(".name").innerText = getName(tab.path); + tabDiv.querySelector(".name").innerText = this.getName(tab.path); tabDiv = tabDiv.nextElementSibling; }) @@ -251,16 +343,16 @@ // 问题是我压根不知道content什么时候加载好 // 解决方法: 轮询设置scrollTop,当连续3次scrollTop不再改变,就判断content加载好了 // 这种方法很不环保,很ugly。但是我确实也想不到在不修改frame.js的前提下该怎么做了 - const scrollContent = activeTab => { + scrollContent = activeTab => { if (!activeTab) return; let count = 0; const stopCount = 3; const scrollTop = activeTab.scrollTop; const _timer = setInterval(() => { - const filePath = global._pluginUtils.getFilePath(); - if (filePath === activeTab.path && entities.content.scrollTop !== scrollTop) { - entities.content.scrollTop = scrollTop; + const filePath = this.utils.getFilePath(); + if (filePath === activeTab.path && this.entities.content.scrollTop !== scrollTop) { + this.entities.content.scrollTop = scrollTop; count = 0; } else { count++; @@ -268,134 +360,77 @@ if (count === stopCount) { clearInterval(_timer); } - }, config.LOOP_DETECT_INTERVAL); + }, this.config.LOOP_DETECT_INTERVAL); } - const openTab = wantOpenPath => { - const pathIdx = tabUtil.tabs.findIndex(tab => tab.path === wantOpenPath); + openTab = wantOpenPath => { + const pathIdx = this.tabUtil.tabs.findIndex(tab => tab.path === wantOpenPath); // 原地打开并且不存在tab时,修改当前tab的文件路径 - if (config.LOCAL_OPEN && pathIdx === -1) { - tabUtil.tabs[tabUtil.activeIdx].path = wantOpenPath; + if (this.config.LOCAL_OPEN && pathIdx === -1) { + this.tabUtil.tabs[this.tabUtil.activeIdx].path = wantOpenPath; } else if (pathIdx === -1) { - tabUtil.tabs.push({path: wantOpenPath, scrollTop: 0}); - tabUtil.activeIdx = tabUtil.tabs.length - 1; + this.tabUtil.tabs.push({path: wantOpenPath, scrollTop: 0}); + this.tabUtil.activeIdx = this.tabUtil.tabs.length - 1; } else if (pathIdx !== -1) { - tabUtil.activeIdx = pathIdx; + this.tabUtil.activeIdx = pathIdx; } - renderDOM(wantOpenPath); + this.renderDOM(wantOpenPath); } - const switchTab = idx => { - tabUtil.activeIdx = idx; - openFile(tabUtil.tabs[tabUtil.activeIdx].path); + switchTab = idx => { + this.tabUtil.activeIdx = idx; + this.openFile(this.tabUtil.tabs[this.tabUtil.activeIdx].path); } - const switchTabByPath = path => { - for (let idx = 0; idx < tabUtil.tabs.length; idx++) { - if (tabUtil.tabs[idx].path === path) { - switchTab(idx); + switchTabByPath = path => { + for (let idx = 0; idx < this.tabUtil.tabs.length; idx++) { + if (this.tabUtil.tabs[idx].path === path) { + this.switchTab(idx); return } } } - const previousTab = () => { - const idx = (tabUtil.activeIdx === 0) ? tabUtil.tabs.length - 1 : tabUtil.activeIdx - 1; - switchTab(idx); + previousTab = () => { + const idx = (this.tabUtil.activeIdx === 0) ? this.tabUtil.tabs.length - 1 : this.tabUtil.activeIdx - 1; + this.switchTab(idx); } - const nextTab = () => { - const idx = (tabUtil.activeIdx === tabUtil.tabs.length - 1) ? 0 : tabUtil.activeIdx + 1; - switchTab(idx); + nextTab = () => { + const idx = (this.tabUtil.activeIdx === this.tabUtil.tabs.length - 1) ? 0 : this.tabUtil.activeIdx + 1; + this.switchTab(idx); } - const closeTab = idx => { - tabUtil.tabs.splice(idx, 1); - if (tabUtil.tabs.length === 0) { - closeWindow(); + closeTab = idx => { + this.tabUtil.tabs.splice(idx, 1); + if (this.tabUtil.tabs.length === 0) { + this.closeWindow(); return } - if (tabUtil.activeIdx !== 0 && idx <= tabUtil.activeIdx) { - tabUtil.activeIdx--; + if (this.tabUtil.activeIdx !== 0 && idx <= this.tabUtil.activeIdx) { + this.tabUtil.activeIdx--; } - switchTab(tabUtil.activeIdx); + this.switchTab(this.tabUtil.activeIdx); } - const closeActiveTab = () => closeTab(tabUtil.activeIdx); - - global._pluginUtils.decorateOpenFile(null, (result, ...args) => { - const filePath = args[0]; - filePath && openTab(filePath); - }) - - global._pluginUtils.loopDetector(() => !!File, () => { - const filePath = global._pluginUtils.getFilePath(); - filePath && openTab(filePath); - }); - - global._pluginUtils.registerWindowHotkey(config.SWITCH_NEXT_TAB_HOTKEY, nextTab); - global._pluginUtils.registerWindowHotkey(config.SWITCH_PREVIOUS_TAB_HOTKEY, previousTab); - global._pluginUtils.registerWindowHotkey(config.CLOSE_HOTKEY, closeActiveTab); + closeActiveTab = () => this.closeTab(this.tabUtil.activeIdx); - entities.tabBar.addEventListener("click", ev => { - const closeButton = ev.target.closest(".close-button"); - const tabContainer = ev.target.closest(".tab-container"); - if (!closeButton && !tabContainer) return; - - ev.stopPropagation(); - ev.preventDefault(); - - const tab = closeButton ? closeButton.closest(".tab-container") : tabContainer; - const idx = parseInt(tab.getAttribute("idx")); - - if (global._pluginUtils.metaKeyPressed(ev)) { - openFileNewWindow(tabUtil.tabs[idx].path, false); - } else if (closeButton) { - closeTab(idx); - } else { - switchTab(idx); - } - }) - - entities.tabBar.addEventListener("wheel", ev => { - const target = ev.target.closest("#plugin-window-tab .tab-bar"); - if (!target) return; - - if (global._pluginUtils.metaKeyPressed(ev)) { - (ev.deltaY < 0) ? previousTab() : nextTab(); - } else { - target.scrollLeft += ev.deltaY; - } - }) - - entities.content.addEventListener("scroll", () => { - tabUtil.tabs[tabUtil.activeIdx].scrollTop = entities.content.scrollTop; - }) - - document.querySelector(".typora-quick-open-list").addEventListener("mousedown", ev => { - const target = ev.target.closest(".typora-quick-open-item"); - if (!target) return; - - // 将原先的click行为改成ctrl+click - if (global._pluginUtils.metaKeyPressed(ev)) return; - - ev.preventDefault(); - ev.stopPropagation(); - const filePath = target.getAttribute("data-path"); - openFile(filePath); - }, true) - - const newWindowIfNeed = (offsetY, tab) => { + newWindowIfNeed = (offsetY, tab) => { offsetY = Math.abs(offsetY); - const height = entities.tabBar.getBoundingClientRect().height; - if (offsetY > height * config.HEIGHT_SCALE) { + const height = this.entities.tabBar.getBoundingClientRect().height; + if (offsetY > height * this.config.HEIGHT_SCALE) { const idx = parseInt(tab.getAttribute("idx")); - const _path = tabUtil.tabs[idx].path; - openFileNewWindow(_path, false); + const _path = this.tabUtil.tabs[idx].path; + this.openFileNewWindow(_path, false); } } - if (config.DRAG_STYLE === 1) { + sort1 = () => { + const newWindowIfNeed = this.newWindowIfNeed; + const tabUtil = this.tabUtil; + const openTab = this.openTab; + const entities = this.entities; + const resetTabBar = () => { const tabs = document.querySelectorAll("#plugin-window-tab .tab-container"); const activeIdx = parseInt(entities.tabBar.querySelector(".tab-container.active").getAttribute("idx")); @@ -416,13 +451,13 @@ tabBar.on("dragstart", ".tab-container", function (ev) { _offsetX = ev.offsetX; currentDragItem = this; - }).on("dragend", ".tab-container", function (ev) { + }).on("dragend", ".tab-container", function () { currentDragItem = null; }).on("dragover", ".tab-container", function (ev) { ev.preventDefault(); if (!currentDragItem) return; this[ev.offsetX > _offsetX ? 'after' : 'before'](currentDragItem); - }).on("dragenter", function (ev) { + }).on("dragenter", function () { return false }) @@ -516,7 +551,12 @@ }) } - if (config.DRAG_STYLE === 2) { + sort2 = () => { + const newWindowIfNeed = this.newWindowIfNeed; + const tabUtil = this.tabUtil; + const openTab = this.openTab; + const entities = this.entities; + const toggleOver = (target, f) => { if (f === "add") { target.classList.add("over"); @@ -556,35 +596,34 @@ }) } - //////////////////////// 以下是声明式插件系统代码 //////////////////////// - const getTabFile = () => global._pluginUtils.joinPath("./plugin/window_tab/save_tabs.json"); + getTabFile = () => this.utils.joinPath("./plugin/window_tab/save_tabs.json"); - const exitTabFile = () => { - const filepath = getTabFile(); + exitTabFile = () => { + const filepath = this.getTabFile(); try { - Package.Fs.accessSync(filepath, Package.Fs.constants.F_OK); + this.utils.Package.Fs.accessSync(filepath, this.utils.Package.Fs.constants.F_OK); return true } catch (err) { } } - const saveTabs = () => { - const dataset = tabUtil.tabs.map((tab, idx) => { + saveTabs = () => { + const dataset = this.tabUtil.tabs.map((tab, idx) => { return { idx: idx, path: tab.path, - active: idx === tabUtil.activeIdx, + active: idx === this.tabUtil.activeIdx, scrollTop: tab.scrollTop, } }) - const filepath = getTabFile(); + const filepath = this.getTabFile(); const str = JSON.stringify({"save_tabs": dataset}, null, "\t"); - Package.Fs.writeFileSync(filepath, str); + this.utils.Package.Fs.writeFileSync(filepath, str); } - const openSaveTabs = () => { - const filepath = getTabFile(); - Package.Fs.readFile(filepath, 'utf8', (error, data) => { + openSaveTabs = () => { + const filepath = this.getTabFile(); + this.utils.Package.Fs.readFile(filepath, 'utf8', (error, data) => { if (error) { window.alert(error); return; @@ -594,9 +633,9 @@ let activePath; tabs.forEach(tab => { - const existTab = tabUtil.tabs.filter(t => t.path === tab.path)[0]; + const existTab = this.tabUtil.tabs.filter(t => t.path === tab.path)[0]; if (!existTab) { - tabUtil.tabs.push({path: tab.path, scrollTop: tab.scrollTop}); + this.tabUtil.tabs.push({path: tab.path, scrollTop: tab.scrollTop}); } else { existTab.scrollTop = tab.scrollTop; } @@ -606,60 +645,38 @@ } }) if (activePath) { - switchTabByPath(activePath); + this.switchTabByPath(activePath); } else { - switchTab(tabUtil.activeIdx); + this.switchTab(this.tabUtil.activeIdx); } }) } - const dynamicCallArgsGenerator = () => { + dynamicCallArgsGenerator = () => { let args = []; - if (!exitTabFile()) { + if (!this.exitTabFile()) { args.push({arg_name: "保存所有的标签页", arg_value: "save_tabs"}); } else { args.push({arg_name: "覆盖保存的标签页", arg_value: "save_tabs"}); args.push({arg_name: "打开保存的标签页", arg_value: "open_save_tabs"}); } - if (config.LOCAL_OPEN) { + if (this.config.LOCAL_OPEN) { args.push({arg_name: "在新标签打开文件", arg_value: "new_tab_open"}); // 空白标签不允许当前标签打开 - } else if (global._pluginUtils.getFilePath()) { + } else if (this.utils.getFilePath()) { args.push({arg_name: "在当前标签打开文件", arg_value: "local_open"}); } return args } - const callMap = { - new_tab_open: () => config.LOCAL_OPEN = false, - local_open: () => config.LOCAL_OPEN = true, - save_tabs: saveTabs, - open_save_tabs: openSaveTabs, - } - const call = type => { - const func = callMap[type]; + call = type => { + const func = this.callMap[type]; func && func(); } +} - module.exports = { - call, - dynamicCallArgsGenerator, - meta: { - call, - openTab, - switchTab, - switchTabByPath, - previousTab, - nextTab, - closeTab, - closeActiveTab, - openFileNewWindow, - openFile, - OpenFileLocal, - closeWindow, - } - }; +module.exports = { + plugin: windowTabBarPlugin +}; - console.log("window_tab.js had been injected"); -})() \ No newline at end of file