├── .gitignore ├── README.md ├── assets └── regex-pipeline-newmenu.gif ├── main.ts ├── manifest.json ├── package.json ├── rollup.config.js ├── samples ├── Append Quote ├── Linebreak with br tag ├── Remove Empty Lines ├── Split After First 。 ├── Table_c2 ├── example_sentences ├── goo ├── index.txt └── nihongosensei ├── styles.css ├── tsconfig.json └── versions.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | main.js 11 | *.js.map 12 | .vscode/ 13 | TestGround/** 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Regex Pipeline 2 | 3 | ![](https://img.shields.io/github/downloads/no3371/obsidian-regex-pipeline/total?style=plastic) 4 | 5 | (Sharing rulesets in Discussions is welcomed) 6 | 7 | Regex Pipeline is an [Obsidian](https://obsidian.md/) plugin that allows users to setup custom regex rules to automatically format notes, this is especially useful in scenerios like building personal knowledge database, because you often clip webpage from same sources. 8 | 9 | ![](https://raw.githubusercontent.com/No3371/obsidian-regex-pipeline/master/assets/regex-pipeline-newmenu.gif) 10 | 11 | ## Usage 12 | 13 | > [Mr. Partan](www.lpartan.com) provided a nice [writeup](https://gist.github.com/No3371/f1750b178376f0659df6650ccaf57c12) about how to use the plugin, I recommend it if you are not familiar with regex or software usage. (September 2021, v1.0.9) 14 | 15 | First, enable the plugin, a file named index.txt should be created at `.obsidian/regex-rulesets/`. Due to how Obsidian protects your disks, you have to specify what ruleset files are there to be read, that's why we need a index file. 16 | 17 | Starting from version 1.0.8, you can add rulesets through the + button in the menu, but you still have to go to `.obsidian/regex-rulesets/` and modify the files you want to edit/remove, because it's hard to provide good editing experience that rivals common editors like VSCode. 18 | 19 | Starting from version 1.1.0, you can apply rulesets via the right-click menu. The available option count can be adjusted in the settings. 20 | 21 | Starting from version 1.2.0, quick rulesets (mentioned right above) can be invoked through Obsidian's command system when "Quick Commands" are set to above 0 in the settings. 22 | 23 | #### Writing Rulesets 24 | A ruleset contains one or more rule, the format looks like: 25 | ``` 26 | :: Any "SEARCH" becomes "REPLACE" 27 | "SEARCH"->"REPLACE" 28 | ``` 29 | 30 | ✅ These work: 31 | ``` 32 | "SEARCH"->"REPLACE" 33 | ``` 34 | 35 | ``` 36 | "SEARCH" 37 | ->"REPLACE" 38 | ``` 39 | 40 | ``` 41 | "SEARCH" 42 | -> 43 | "REPLACE" 44 | ``` 45 | 46 | ``` 47 | "SEARCH"-> 48 | "REPLACE" 49 | ``` 50 | 51 | ❌ These do NOT work (Empty line inbetween not allowed; Nothing except new line is allowed right before and after the `->`) 52 | 53 | ``` 54 | "SEARCH" 55 | 56 | -> 57 | "REPLACE" 58 | ``` 59 | 60 | ``` 61 | "SEARCH"-> 62 | 63 | "REPLACE" 64 | ``` 65 | 66 | ``` 67 | "SEARCH" 68 | -> 69 | 70 | "REPLACE" 71 | ``` 72 | 73 | #### Multi-line replacement string: 74 | ``` 75 | "SEARCH"->"REP 76 | LACE" 77 | :: Any "SEARCH" becomes "REP 78 | :: LACE" 79 | ``` 80 | 81 | #### Regex Flags 82 | By default, `gm` (multiline) flag is appended to the **SEARCH** regex, you can overwrite this by providing your own flags, for example, use `gmu` flag in this way: 83 | ``` 84 | "SEARCH"gmu->"REPLACE" 85 | ``` 86 | 87 | Noted that `gm` flags are bascially neccessary for this plugin to be useful, you seldom wants to replace only 1 occurances or operate on a note only contains 1 line. 88 | 89 | #### Replace With Nothing 90 | ``` 91 | "SEARCH"->"" 92 | :: Any "SEARCH" becomes "" 93 | ``` 94 | Basically this is how we remove matched content. 95 | 96 | 97 | #### Indexing 98 | Rulesets must be saved in `.obsidian/regex-rulesets/`, and must be included in the `index.txt`, one file per line. The order of the lines determines the displaying order of the rulesets in the menu. 99 | 100 | #### Applying Rulesets 101 | Press the sidebar button of this plugin to show the rulesets menu, select your ruleset, and it'll be applied. 102 | 103 | The menu is available as a command so you can also bind it to a shortcut. 104 | 105 | **Note**: The plugin support applying rules to selection, if anything is selected, only selection is modified! 106 | 107 | ## Examples 108 | 109 | **NumberToAlphabet** 110 | .obsidian/regex-rulesets/number-to-alphabet.txt 111 | ``` 112 | "1"->"A" 113 | "2"->"B" 114 | "3"->"C" 115 | "4"->"D" 116 | "5"->"E" 117 | "6"->"F" 118 | "7"->"G" 119 | "8"->"H" 120 | "9"->"I" 121 | ``` 122 | 123 | **Table_c2** 124 | This ruleset help you transform selected content into a table of 2 columns! Every 2 non-empty line will form a row. 125 | 126 | .obsidian/regex-rulesets/Table_c2 127 | ``` 128 | "^(.+)$\n\n^(.+)$"->"| $1 | $2 |" 129 | ``` 130 | 131 | **Linebreak with br tag** 132 | This ruleset replace all newline with `
`. 133 | 134 | ``` 135 | "^(.+)\s+?\n(?=^.+)"->"$1
" 136 | ``` 137 | 138 | Take a look in [samples folder](https://github.com/No3371/obsidian-regex-pipeline/tree/master/samples) for more examples, including a very complex one like the above gif! 139 | 140 | ## Recommendations 141 | - Markdownload (https://github.com/deathau/markdownload): for clipping webpages, don't forget to configure it to match your editing preferences. 142 | 143 | ## FAQ 144 | #### My ruleset file doesn't work,The notification says there's 0 replacement, but I'm sure the format is correct. 145 | It's possible that your ruleset file is in non-UTF8 encoding, this happens with some editor applications, please refer to [#12](https://github.com/No3371/obsidian-regex-pipeline/issues/12). 146 | -------------------------------------------------------------------------------- /assets/regex-pipeline-newmenu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/No3371/obsidian-regex-pipeline/b4815ecee5de9f750ea775eb954581c481d75e9f/assets/regex-pipeline-newmenu.gif -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { App, BaseComponent, ButtonComponent, Component, EventRef, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TextAreaComponent, TextComponent, TFile, Vault, Command, Editor, Hotkey } from 'obsidian'; 2 | 3 | export default class RegexPipeline extends Plugin { 4 | rules: string[] 5 | pathToRulesets = this.app.vault.configDir + "/regex-rulesets"; 6 | indexFile = "/index.txt" 7 | menu: ApplyRuleSetMenu 8 | configs: SavedConfigs 9 | rightClickEventRef: EventRef 10 | quickCommands : Command[] 11 | quickRulesChanged : boolean 12 | 13 | log (message?: any, ...optionalParams: any[]) 14 | { 15 | // comment this to disable logging 16 | console.log("[regex-pipeline] " + message); 17 | } 18 | 19 | async onload() { 20 | this.log('loading'); 21 | this.addSettingTab(new ORPSettings(this.app, this)) 22 | this.configs = await this.loadData() 23 | if (this.configs == null) this.configs = new SavedConfigs(3, 3, false) 24 | if (this.configs.rulesInVault) this.pathToRulesets = "/regex-rulesets" 25 | this.menu = new ApplyRuleSetMenu(this.app, this) 26 | this.menu.contentEl.className = "rulesets-menu-content" 27 | this.menu.titleEl.className = "rulesets-menu-title" 28 | 29 | this.addRibbonIcon('dice', 'Regex Rulesets', () => { 30 | this.menu.open(); 31 | }); 32 | 33 | this.addCommand({ 34 | id: 'apply-ruleset', 35 | name: 'Apply Ruleset', 36 | // callback: () => { 37 | // this.log('Simple Callback'); 38 | // }, 39 | checkCallback: (checking: boolean) => { 40 | let leaf = this.app.workspace.activeLeaf; 41 | if (leaf) { 42 | if (!checking) { 43 | this.menu.open(); 44 | } 45 | return true; 46 | } 47 | return false; 48 | } 49 | }); 50 | 51 | this.reloadRulesets(); 52 | this.log("Rulesets: " + this.pathToRulesets); 53 | this.log("Index: " + this.pathToRulesets + this.indexFile); 54 | 55 | } 56 | 57 | onunload() { 58 | this.log('unloading'); 59 | if (this.rightClickEventRef != null) this.app.workspace.offref(this.rightClickEventRef) 60 | } 61 | 62 | async reloadRulesets() { 63 | if (!await this.app.vault.adapter.exists(this.pathToRulesets)) 64 | await this.app.vault.createFolder(this.pathToRulesets) 65 | if (!await this.app.vault.adapter.exists(this.pathToRulesets + this.indexFile)) 66 | await this.app.vault.adapter.write(this.pathToRulesets + this.indexFile, "").catch((r) => { 67 | new Notice("Failed to write to index file: " + r) 68 | }); 69 | 70 | let p = this.app.vault.adapter.read(this.pathToRulesets + this.indexFile); 71 | p.then(s => { 72 | this.rules = s.split(/\r\n|\r|\n/); 73 | this.rules = this.rules.filter((v) => v.length > 0); 74 | this.log(this.rules); 75 | this.updateRightclickMenu(); 76 | this.updateQuickCommands(); 77 | }) 78 | } 79 | 80 | async updateQuickCommands () { 81 | if (this.configs.quickCommands <= 0) return; 82 | if (this.quickCommands == null) this.quickCommands = new Array(); 83 | let expectedCommands = Math.min(this.configs.quickCommands, this.rules.length); 84 | // this.log(`setting up ${expectedCommands} commands...`) 85 | for (let i = 0; i < expectedCommands; i++) 86 | { 87 | let r = this.rules[i]; 88 | let c = this.addCommand({ 89 | id: `ruleset: ${r}`, 90 | name: r, 91 | editorCheckCallback: (checking: boolean) => { 92 | if (checking) return this.rules.contains(r); 93 | this.applyRuleset(this.pathToRulesets + "/" + r); 94 | }, 95 | }); 96 | // this.log(`pusing ${r} command...`) 97 | this.quickCommands.push(c); 98 | this.log(this.quickCommands) 99 | } 100 | } 101 | 102 | async updateRightclickMenu () { 103 | if (this.rightClickEventRef != null) this.app.workspace.offref(this.rightClickEventRef) 104 | this.rightClickEventRef = this.app.workspace.on("editor-menu", (menu) => { 105 | for (let i = 0; i < Math.min(this.configs.quickRules, this.rules.length); i++) 106 | { 107 | let rPath = this.pathToRulesets + "/" + this.rules[i] 108 | 109 | menu.addItem((item) => { 110 | item.setTitle("Regex Pipeline: " + this.rules[i]) 111 | .onClick(() => { 112 | this.applyRuleset(rPath) 113 | }); 114 | }); 115 | } 116 | }) 117 | this.registerEvent(this.rightClickEventRef) 118 | } 119 | 120 | async appendRulesetsToIndex(name : string) : Promise { 121 | var result : boolean = true 122 | this.rules.push(name) 123 | var newIndexValue = ""; 124 | this.rules.forEach((v, i, all) => { 125 | newIndexValue += v + "\n" 126 | }) 127 | await this.app.vault.adapter.write(this.pathToRulesets + this.indexFile, newIndexValue).catch((r) => { 128 | new Notice("Failed to write to index file: " + r) 129 | result = false; 130 | }); 131 | 132 | return result; 133 | } 134 | 135 | async createRuleset (name : string, content : string) : Promise { 136 | var result : boolean = true 137 | this.log("createRuleset: " + name); 138 | var path = this.pathToRulesets + "/" + name; 139 | if (await this.app.vault.adapter.exists(path)) { 140 | this.log("file existed: " + path); 141 | return false; 142 | } 143 | 144 | await this.app.vault.adapter.write(path, content).catch((r) => { 145 | new Notice("Failed to write the ruleset file: " + r) 146 | result = false; 147 | }); 148 | 149 | result = await this.appendRulesetsToIndex(name) 150 | return true; 151 | } 152 | 153 | async applyRuleset (ruleset : string) { 154 | if (!await this.app.vault.adapter.exists(ruleset)) { 155 | new Notice(ruleset + " not found!"); 156 | return 157 | } 158 | let ruleParser = /^"(.+?)"([a-z]*?)(?:\r\n|\r|\n)?->(?:\r\n|\r|\n)?"(.*?)"([a-z]*?)(?:\r\n|\r|\n)?$/gmus; 159 | let ruleText = await this.app.vault.adapter.read(ruleset); 160 | 161 | let activeMarkdownView = this.app.workspace.getActiveViewOfType(MarkdownView); 162 | if (activeMarkdownView == null) 163 | { 164 | new Notice("No active Markdown file!"); 165 | return; 166 | } 167 | 168 | let subject; 169 | let selectionMode; 170 | if (activeMarkdownView.editor.somethingSelected()) 171 | { 172 | subject = activeMarkdownView.editor.getSelection(); 173 | selectionMode = true; 174 | } 175 | else 176 | { 177 | subject = activeMarkdownView.editor.getValue(); 178 | } 179 | 180 | let pos = activeMarkdownView.editor.getScrollInfo() 181 | this.log(pos.top) 182 | 183 | let count = 0; 184 | let ruleMatches; 185 | while (ruleMatches = ruleParser.exec(ruleText)) 186 | { 187 | if (ruleMatches == null) break; 188 | this.log("\n" + ruleMatches[1] + "\n↓↓↓↓↓\n"+ ruleMatches[3]); 189 | 190 | let matchRule = ruleMatches[2].length == 0? new RegExp(ruleMatches[1], 'gm') : new RegExp(ruleMatches[1], ruleMatches[2]); 191 | if (ruleMatches[4] == 'x') subject = subject.replace(matchRule, ''); 192 | else subject = subject.replace(matchRule, ruleMatches[3]); 193 | count++; 194 | } 195 | if (selectionMode) 196 | activeMarkdownView.editor.replaceSelection(subject); 197 | else 198 | activeMarkdownView.editor.setValue(subject); 199 | 200 | activeMarkdownView.requestSave(); 201 | activeMarkdownView.editor.scrollTo(0, pos.top) 202 | new Notice("Executed ruleset '" + ruleset + "' which contains " + count + " regex replacements!"); 203 | 204 | } 205 | } 206 | 207 | class SavedConfigs { 208 | constructor(quickRules: number, quickCommands : number, rulesInVault: boolean) { 209 | this.quickRules = quickRules 210 | this.rulesInVault = rulesInVault 211 | this.quickCommands = quickCommands 212 | } 213 | quickRules: number 214 | quickCommands : number 215 | rulesInVault: boolean 216 | } 217 | 218 | class ORPSettings extends PluginSettingTab { 219 | 220 | plugin: RegexPipeline; 221 | constructor(app: App, plugin: RegexPipeline) { 222 | super(app, plugin); 223 | } 224 | 225 | quickRulesCache : number 226 | 227 | display() { 228 | this.containerEl.empty() 229 | new Setting(this.containerEl) 230 | .setName("Quick Rules") 231 | .setDesc("The first N rulesets in your index file will be available in the right click menu.") 232 | .addSlider(c => { 233 | c.setValue(this.plugin.configs.quickRules) 234 | c.setLimits(0, 10, 1) 235 | c.setDynamicTooltip() 236 | c.showTooltip() 237 | c.onChange((v) => { 238 | if (v != this.plugin.configs.quickRules) this.plugin.quickRulesChanged = true; 239 | this.plugin.configs.quickRules = v; 240 | }) 241 | }) 242 | new Setting(this.containerEl) 243 | .setName("Quick Rule Commands") 244 | .setDesc("The first N rulesets in your index file will be available as Obsidian commands. When changing this count or re-ordering rules, existing commands will not be removed until next reload (You can also manually re-enable the plugin).") 245 | .addSlider(c => { 246 | c.setValue(this.plugin.configs.quickCommands) 247 | c.setLimits(0, 10, 1) 248 | c.setDynamicTooltip() 249 | c.showTooltip() 250 | c.onChange((v) => { 251 | this.plugin.configs.quickCommands = v; 252 | this.plugin.updateQuickCommands(); 253 | }) 254 | }) 255 | new Setting(this.containerEl) 256 | .setName("Save Rules In Vault") 257 | .setDesc("Reads rulesets from \".obsidian/regex-rulesets\" when off, \"./regex-ruleset\" when on (useful if you are user of ObsidianSync). ") 258 | .addToggle(c => { 259 | c.setValue(this.plugin.configs.rulesInVault) 260 | c.onChange(v => { 261 | this.plugin.configs.rulesInVault = v 262 | if (v) this.plugin.pathToRulesets = "/regex-rulesets" 263 | else this.plugin.pathToRulesets = this.app.vault.configDir + "/regex-rulesets" 264 | }) 265 | }) 266 | } 267 | 268 | hide () { 269 | this.plugin.reloadRulesets() 270 | this.plugin.saveData(this.plugin.configs) 271 | } 272 | 273 | } 274 | 275 | class ApplyRuleSetMenu extends Modal { 276 | plugin: RegexPipeline; 277 | constructor(app: App, plugin: RegexPipeline) { 278 | super(app); 279 | this.plugin = plugin; 280 | this.modalEl.style.setProperty("width", "60vw"); 281 | this.modalEl.style.setProperty("max-height", "60vh"); 282 | this.modalEl.style.setProperty("padding", "2rem"); 283 | this.titleEl.createEl("h1", null, el => { 284 | el.innerHTML = this.plugin.pathToRulesets + "/..."; 285 | el.style.setProperty("display", "inline-block"); 286 | el.style.setProperty("width", "92%"); 287 | el.style.setProperty("max-width", "480px"); 288 | el.style.setProperty("margin", "12 0 8"); 289 | }); 290 | this.titleEl.createEl("h1", null, el => { el.style.setProperty("flex-grow", "1") }); 291 | var reloadButton = new ButtonComponent(this.titleEl) 292 | .setButtonText("RELOAD") 293 | .onClick(async (evt) => { 294 | await this.plugin.reloadRulesets(); 295 | this.onClose(); 296 | this.onOpen(); 297 | }); 298 | reloadButton.buttonEl.style.setProperty("display", "inline-block") 299 | reloadButton.buttonEl.style.setProperty("bottom", "8px") 300 | reloadButton.buttonEl.style.setProperty("margin", "auto") 301 | } 302 | 303 | onOpen() { 304 | for (let i = 0; i < this.plugin.rules.length; i++) 305 | { 306 | // new Setting(contentEl) 307 | // .setName(this.plugin.rules[i]) 308 | // .addButton(btn => btn.onClick(async () => { 309 | // this.plugin.applyRuleset(this.plugin.pathToRulesets + "/" + this.plugin.rules[i]) 310 | // this.close(); 311 | // }).setButtonText("Apply")); 312 | var ruleset = new ButtonComponent(this.contentEl) 313 | .setButtonText(this.plugin.rules[i]) 314 | .onClick(async (evt) => { 315 | this.plugin.applyRuleset(this.plugin.pathToRulesets + "/" + this.plugin.rules[i]) 316 | this.close(); 317 | }); 318 | ruleset.buttonEl.className = "apply-ruleset-button"; 319 | } 320 | this.titleEl.getElementsByTagName("h1")[0].innerHTML = this.plugin.pathToRulesets + "/..."; 321 | var addButton = new ButtonComponent(this.contentEl) 322 | .setButtonText("+") 323 | .onClick(async (evt) => { 324 | new NewRulesetPanel(this.app, this.plugin).open(); 325 | }); 326 | addButton.buttonEl.className = "add-ruleset-button"; 327 | addButton.buttonEl.style.setProperty("width", "3.3em"); 328 | } 329 | 330 | onClose() { 331 | let {contentEl} = this; 332 | contentEl.empty(); 333 | } 334 | } 335 | 336 | class NewRulesetPanel extends Modal { 337 | 338 | plugin: RegexPipeline; 339 | constructor(app: App, plugin: RegexPipeline) { 340 | super(app); 341 | this.plugin = plugin; 342 | this.contentEl.className = "ruleset-creation-content" 343 | } 344 | 345 | onOpen() { 346 | var nameHint = this.contentEl.createEl("h4"); 347 | nameHint.innerHTML = "Name"; 348 | this.contentEl.append(nameHint); 349 | var nameInput = this.contentEl.createEl("textarea"); 350 | nameInput.setAttr("rows", "1"); 351 | nameInput.addEventListener('keydown', (e) => { 352 | if (e.key === "Enter") e.preventDefault(); 353 | }); 354 | this.contentEl.append(nameInput); 355 | var contentHint = this.contentEl.createEl("h4"); 356 | contentHint.innerHTML = "Content"; 357 | this.contentEl.append(contentHint); 358 | var contentInput = this.contentEl.createEl("textarea"); 359 | contentInput.style.setProperty("height", "300px"); 360 | this.contentEl.append(contentInput); 361 | var saveButton = new ButtonComponent(this.contentEl) 362 | .setButtonText("Save") 363 | .onClick(async (evt) => { 364 | if (!await this.plugin.createRuleset(nameInput.value, contentInput.value)) 365 | { 366 | new Notice("Failed to create the ruleset! Please check if the file already exist."); 367 | return 368 | } 369 | this.plugin.menu.onClose(); 370 | this.plugin.menu.onOpen(); 371 | this.close() 372 | }); 373 | } 374 | 375 | onClose() { 376 | let {contentEl} = this; 377 | contentEl.empty(); 378 | } 379 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-regex-pipeline", 3 | "name": "Regex Pipeline", 4 | "version": "1.4.0", 5 | "minAppVersion": "0.12.19", 6 | "description": "Allows users setup custom regex rules to automatically format notes.", 7 | "author": "No3371", 8 | "authorUrl": "github.com/No3371", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "1.4.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^15.1.0", 15 | "@rollup/plugin-node-resolve": "^9.0.0", 16 | "@rollup/plugin-typescript": "^6.0.0", 17 | "@types/node": "^14.14.2", 18 | "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master", 19 | "rollup": "^2.32.1", 20 | "tslib": "^2.0.3", 21 | "typescript": "^4.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 8 | if you want to view the source visit the plugins github repository 9 | */ 10 | `; 11 | 12 | export default { 13 | input: 'main.ts', 14 | output: { 15 | dir: '.', 16 | sourcemap: 'inline', 17 | format: 'cjs', 18 | exports: 'default', 19 | banner, 20 | }, 21 | external: ['obsidian'], 22 | plugins: [ 23 | typescript(), 24 | nodeResolve({browser: true}), 25 | commonjs(), 26 | ] 27 | }; -------------------------------------------------------------------------------- /samples/Append Quote: -------------------------------------------------------------------------------- 1 | "^(.+)$"->">$1" 2 | 3 | :: A 4 | :: ↓↓↓ 5 | :: >A -------------------------------------------------------------------------------- /samples/Linebreak with br tag: -------------------------------------------------------------------------------- 1 | "^(.+)\s+?\n(?=^.+)"->"$1
" 2 | 3 | :: ABCD 4 | :: 5 | :: A 6 | :: 7 | :: ABCD 8 | :: A 9 | :: B 10 | :: ↓↓↓ 11 | :: ABCD
A
ABCD 12 | :: A 13 | :: B -------------------------------------------------------------------------------- /samples/Remove Empty Lines: -------------------------------------------------------------------------------- 1 | "^\s*\n"->"ANY"x -------------------------------------------------------------------------------- /samples/Split After First 。: -------------------------------------------------------------------------------- 1 | "^(.+?)。"->"$1。 2 | " 3 | 4 | :: 一二三。ABC. 5 | :: ↓↓↓ 6 | :: 一二三。 7 | :: ABC. -------------------------------------------------------------------------------- /samples/Table_c2: -------------------------------------------------------------------------------- 1 | "^(.+)$\n\n^(.+)$"->"| $1 | $2 |" 2 | 3 | :: 一二三。 4 | :: 5 | :: ABC. 6 | :: ↓↓↓ 7 | :: | 一二三。 | ABC. | -------------------------------------------------------------------------------- /samples/example_sentences: -------------------------------------------------------------------------------- 1 | "^\d\.\s*(.+)$\s*(.+)$"->">$1 2 | $2 3 | " -------------------------------------------------------------------------------- /samples/goo: -------------------------------------------------------------------------------- 1 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02539\.gif\)"->"![[@1.gif]]" 2 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02540\.gif\)"->"![[@2.gif]]" 3 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02541\.gif\)"->"![[@3.gif]]" 4 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02542\.gif\)"->"![[@4.gif]]" 5 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02543\.gif\)"->"![[@5.gif]]" 6 | "\!\[\]\(https://dictionary\.goo\.ne\.jp/img/daijisen/gaiji/02544\.gif\)"->"![[@6.gif]]" 7 | 8 | ::This fix example sentence right after a topic 9 | "[(.+?)](.+)\n\n1\. 1\. 「"->"[$1]$2 10 | >「" 11 | 12 | ::This fix example sentence right after a topic 13 | "[(.+?)](.+)\n\n- 「"->"[$1]$2 14 | >「" 15 | 16 | 17 | ::This extract entry name from the title 18 | "^#\s.+goo国語辞書\n+(.+)\n+の解説\n+\-+"->"# $1" 19 | "^# (.+)の意味・使い方 \- 四字熟語一覧 \- goo辞書"->"# $1" 20 | 21 | ::This simplify the source of the phrase 22 | "(.+)の解説 \- (.+)$\n--------------------------------------------------------------------------------------------------\n\n.+\n--------------"->"#### $2" 23 | 24 | ::This extract titles for sub entries2 25 | "(.+?)\n\nの解説\n+?\-+"->"--- 26 | # $1" 27 | 28 | :: This convert these lines to headers 29 | "^類語$"->"#### 類語" 30 | "^関連語$"->"#### 関連語" 31 | "^下接句$"->"#### 下接句" 32 | "^出典$"->"#### 類語" 33 | "^句例$"->"#### 関連語" 34 | "^用例$"->"#### 下接句" 35 | "^対義語$"->"#### 類語" 36 | "^活用形$"->"#### 関連語" 37 | 38 | :: Convert all second level single example sentence 39 | " [\n\s]? 1\.\s+「(?!.+\n\s+?2\.)"->" >「" 40 | 41 | :: These 2 fix arrow reference 42 | " >→\[(.+?)\]\((.+)\)[(.+?)]"->" →[$1]($2)[$3]" 43 | " 1\. →\[(.+?)\]\((.+)\)\s?[(.+?)]"->" →[$1]($2)[$3]" 44 | 45 | 46 | :: This fix \n\n → to \n → 47 | "[\n\s]+?(?= →\[.+?\]\(.+\)[.+?])"->" 48 | " 49 | 50 | 51 | "^ 「"->" >「" 52 | 53 | :: This fix wrong bullets captured by [MardDownload](github.com/deathau/markdownload) 54 | "^1\. \*\*1\*\*"->"1. " 55 | "^1\. \*\*2\*\*"->"2. " 56 | "^1\. \*\*3\*\*"->"3. " 57 | "^1\. \*\*4\*\*"->"4. " 58 | "^1\. \*\*5\*\*"->"5. " 59 | "^1\. \*\*6\*\*"->"6. " 60 | "^1\. \*\*7\*\*"->"7. " 61 | "^1\. \*\*8\*\*"->"8. " 62 | "^1\. \*\*9\*\*"->"9. " 63 | "^1\. \*\*10\*\*"->"10. " 64 | "^1\. \*\*11\*\*"->"11. " 65 | "^1\. \*\*12\*\*"->"12. " 66 | "^1\. \*\*13\*\*"->"13. " 67 | "^1\. \*\*14\*\*"->"14. " 68 | "^1\. \*\*15\*\*"->"15. " 69 | "^1\. \*\*16\*\*"->"16. " 70 | "^1\. \*\*17\*\*"->"17. " 71 | "^1\. \*\*18\*\*"->"18. " 72 | "^1\. \*\*19\*\*"->"19. " 73 | "^1\. \*\*20\*\*"->"20. " 74 | "^1\. \*\*21\*\*"->"21. " 75 | "^1\. \*\*22\*\*"->"22. " 76 | "^1\. \*\*23\*\*"->"23. " 77 | "^1\. \*\*24\*\*"->"24. " 78 | "^1\. \*\*25\*\*"->"25. " 79 | "^1\. \*\*26\*\*"->"26. " 80 | "^1\. \*\*27\*\*"->"27. " 81 | "^1\. \*\*28\*\*"->"28. " 82 | "^1\. \*\*29\*\*"->"29. " 83 | "^1\. \*\*30\*\*"->"30. " 84 | "^(\d+?)\. "->"$1. " 85 | 86 | :: This fix 2 space after second level bullets: 'n. ' becomes 'n. ' 87 | " (\d+?)\. "->" $1. " 88 | 89 | ::This clears empty lines. 90 | "\n\s+?\n (\d+?)\. "->" 91 | $1. " 92 | 93 | ::This clears empty lines. 94 | "\n\s+?\n >「"->" 95 | >「" 96 | 97 | ::This clears empty lines. 98 | "^\s+\n(\d+)\."->"$1." 99 | 100 | " (\d+?)\. 「"->" $1. >「" 101 | 102 | "^(.+) \- (.+) (.+)\n^\-+$"->"### $2 $3" -------------------------------------------------------------------------------- /samples/index.txt: -------------------------------------------------------------------------------- 1 | goo 2 | nihongosensei 3 | example_sentences 4 | Table_c2 5 | Remove Empty Lines 6 | Append Quote 7 | Split After First 。 8 | Connect lines with
9 | Linebreak with br tag 10 | -------------------------------------------------------------------------------- /samples/nihongosensei: -------------------------------------------------------------------------------- 1 | "^#\s(.+)\s\|\s毎日のんびり日本語教師"->"# $1" 2 | 3 | "^説明(?=\n)"->"## 説明" 4 | "^備考(?=\n)"->"## 備考" 5 | 6 | 7 | "^接続(?=\n)"->"### 接続" 8 | "^意味(?=\n)"->"### 意味" 9 | "^解説(?=\n)"->"### 解説" 10 | "^例文(?=\n)"->"### 例文" 11 | 12 | "^ (?=.+?\n)"->"a"x 13 | 14 | ::This format examples, every example is 1 line of nihongo followed by 1 line of chinese 15 | "^(.+?) (.+)\s?\n[ ]+?((.+))\s?"->">$1 16 | >($2) 17 | " 18 | 19 | ::These format "correct" examples 20 | "^(.+?)◯(.+)\s?\n[ ]+?((.+))\s?"->">◯ $1 21 | >($2) 22 | " 23 | "^(.+?)◯(.+)$"->">◯ $1 24 | " 25 | 26 | ::These format "incorrect" examples 27 | "^(.+?)✕(.+)\s?\n[ ]+?((.+))\s?"->">✕ $1 28 | >($2) 29 | " 30 | "^(.+?)✕(.+)$"->">✕ $1 31 | " -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .rulesets-menu-content { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: row; 6 | flex-wrap: wrap; 7 | align-content: flex-start; 8 | gap: 0.3rem; 9 | } 10 | 11 | .add-ruleset-button, .apply-ruleset-button { 12 | height: 3.3em; 13 | font-size: large; 14 | font-weight: 800; 15 | background-color: transparent !important; 16 | color: var(--interactive-accent); 17 | box-sizing: border-box; 18 | margin: 6px 6px 6px 0; 19 | } 20 | 21 | .add-ruleset-button:hover, .apply-ruleset-button:hover { 22 | font-size: large; 23 | box-shadow: 0 0 1px 3px var(--interactive-accent-hover) inset; 24 | color: var(--interactive-accent-hover); 25 | } 26 | 27 | .ruleset-creation-content { 28 | display: flex; 29 | flex-direction: column; 30 | } 31 | 32 | .ruleset-creation-content h4 { 33 | margin: 12px 0 6px 0; 34 | } 35 | 36 | .ruleset-creation-content button { 37 | margin: 12px 0 0 0; 38 | } 39 | 40 | .ruleset-creation-content input { 41 | background-color: transparent; 42 | box-shadow: 0 0 1px 2px var(--interactive-accent) inset; 43 | } 44 | 45 | .ruleset-creation-content textarea { 46 | background-color: transparent; 47 | box-shadow: 0 0 1px 2px var(--interactive-accent) inset; 48 | } 49 | 50 | .rulesets-menu-title { 51 | display: flex; 52 | flex-direction: row; 53 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.9": "0.9.12", 3 | "1.1.0": "0.12.19" 4 | } 5 | --------------------------------------------------------------------------------