├── versions.json ├── .gitignore ├── .prettierrc.json ├── README.md ├── workspace.code-workspace ├── manifest.json ├── tsconfig.json ├── .github ├── dependabot.yml └── workflows │ └── release.yml ├── package.json ├── rollup.config.js └── main.ts /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.9.0": "0.9.12" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | 8 | # build 9 | main.js 10 | *.js.map 11 | 12 | # obsidian 13 | data.json 14 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | support `《》 【】()‘’ “” 「」` auto pair 4 | 5 | # 特性 6 | 7 | 支持 `《》 【】()‘’ “” 「」` 符号输入时自动补齐 8 | 9 | # TODO 10 | 11 | - [x] 当出现成对符号时,会一起直接进行删除 12 | - [ ] 设置允许 ignore 名单 13 | - [x] 选中文字后嵌套符号 14 | - [x] 下单引号和下双引号自动向前补齐 15 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.formatOnSave": true, 10 | "editor.tabSize": 2 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-auto-pair-chinese-symbol", 3 | "name": "Auto pair chinese symbol", 4 | "version": "0.9.5", 5 | "minAppVersion": "0.9.12", 6 | "description": "Auto pair chinese symbol", 7 | "author": "renmu123", 8 | "authorUrl": "https://github.com/renmu123/obsidian-auto-pair-chinese-symbol", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /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": ["dom", "es5", "scripthost", "es2015", "esnext"] 13 | }, 14 | "include": ["**/*.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-pair-chinese-symbol", 3 | "version": "0.9.5", 4 | "description": "auto-pair-chinese-symbol", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js --environment BUILD:production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^21.0.1", 15 | "@rollup/plugin-node-resolve": "^13.1.1", 16 | "@rollup/plugin-typescript": "^8.3.0", 17 | "@types/node": "^17.0.17", 18 | "obsidian": "^0.13.21", 19 | "rollup": "^2.67.2", 20 | "tslib": "^2.3.1", 21 | "typescript": "^4.5.4" 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 isProd = (process.env.BUILD === 'production'); 6 | 7 | const banner = 8 | `/* 9 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 10 | if you want to view the source visit the plugins github repository 11 | */ 12 | `; 13 | 14 | export default { 15 | input: 'main.ts', 16 | output: { 17 | dir: '.', 18 | sourcemap: 'inline', 19 | sourcemapExcludeSources: isProd, 20 | format: 'cjs', 21 | exports: 'default', 22 | banner, 23 | }, 24 | external: ['obsidian'], 25 | plugins: [ 26 | typescript(), 27 | nodeResolve({browser: true}), 28 | commonjs(), 29 | ] 30 | }; -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | PLUGIN_NAME: dist 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: "14" 21 | - name: Build 22 | id: build 23 | run: | 24 | npm install 25 | npm run build 26 | mkdir ${{ env.PLUGIN_NAME }} 27 | cp main.js manifest.json ${{ env.PLUGIN_NAME }} 28 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 29 | ls 30 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 31 | - name: Create Release 32 | id: create_release 33 | uses: actions/create-release@v1 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | with: 37 | tag_name: ${{ github.ref }} 38 | release_name: ${{ github.ref }} 39 | draft: false 40 | prerelease: false 41 | - name: Upload zip file 42 | id: upload-zip 43 | uses: actions/upload-release-asset@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | upload_url: ${{ steps.create_release.outputs.upload_url }} 48 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 49 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 50 | asset_content_type: application/zip 51 | - name: Upload main.js 52 | id: upload-main 53 | uses: actions/upload-release-asset@v1 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | upload_url: ${{ steps.create_release.outputs.upload_url }} 58 | asset_path: ./main.js 59 | asset_name: main.js 60 | asset_content_type: text/javascript 61 | - name: Upload manifest.json 62 | id: upload-manifest 63 | uses: actions/upload-release-asset@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | upload_url: ${{ steps.create_release.outputs.upload_url }} 68 | asset_path: ./manifest.json 69 | asset_name: manifest.json 70 | asset_content_type: application/json 71 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { App, Plugin, PluginSettingTab, Setting } from "obsidian"; 2 | 3 | interface MyPluginSettings { 4 | allowSelectEmbed: boolean; 5 | allowQuote: boolean; 6 | } 7 | 8 | const DEFAULT_SETTINGS: MyPluginSettings = { 9 | allowSelectEmbed: false, 10 | allowQuote: true, 11 | }; 12 | 13 | interface Pair { 14 | left: string; 15 | right: string; 16 | code: string; 17 | shiftKey: boolean; 18 | } 19 | 20 | export default class AutoPairPlugin extends Plugin { 21 | settings: MyPluginSettings; 22 | 23 | pairs: { [name: string]: string } = { 24 | "【": "】", 25 | "《": "》", 26 | "(": ")", 27 | "‘": "’", 28 | "“": "”", 29 | "「": "」", 30 | }; 31 | 32 | async onload() { 33 | await this.loadSettings(); 34 | this.addSettingTab(new SampleSettingTab(this.app, this)); 35 | this.registerCodeMirror(cm => { 36 | cm.on("change", this.change); 37 | }); 38 | this.registerCodeMirror(cm => { 39 | cm.on("beforeChange", this.beforeChange); 40 | }); 41 | } 42 | change = (cm: CodeMirror.Editor, obj: CodeMirror.EditorChange) => { 43 | const symbol = obj.text[0]; 44 | const cursorInfo = cm.getCursor(); 45 | const pair = this.pairs[symbol]; 46 | 47 | if (pair) { 48 | // 当有配对的符号时,替换后一个符号为配对的符号 49 | cm.replaceRange(pair, cursorInfo, cursorInfo, "*composeSymbolAdd"); 50 | cm.setCursor(cursorInfo); 51 | return; 52 | } 53 | 54 | if (obj.origin === "+delete") { 55 | const pair = this.pairs[obj.removed[0]]; 56 | if (pair === undefined) { 57 | return; 58 | } 59 | 60 | const value = cm.getRange( 61 | { line: obj.from.line, ch: obj.from.ch }, 62 | { line: obj.from.line, ch: obj.from.ch + 1 } 63 | ); 64 | 65 | if (value !== pair) { 66 | return; 67 | } 68 | 69 | const cur = cm.getCursor(); 70 | cm.replaceRange( 71 | "", 72 | { line: cur.line, ch: cur.ch }, 73 | { line: cur.line, ch: cur.ch + 1 }, 74 | "+delete" 75 | ); 76 | } 77 | // 处理双击【【后软件的自动处理 78 | if (obj.removed[0] === "【【") { 79 | const value = cm.getRange( 80 | { line: obj.from.line, ch: obj.from.ch + 2 }, 81 | { line: obj.from.line, ch: obj.from.ch + 4 } 82 | ); 83 | if (value === "】】") { 84 | cm.replaceRange( 85 | "", 86 | { line: cursorInfo.line, ch: cursorInfo.ch }, 87 | { line: cursorInfo.line, ch: cursorInfo.ch + 2 }, 88 | "+delete" 89 | ); 90 | } 91 | } 92 | 93 | if (this.settings.allowQuote && obj.origin !== "*composeSymbolAdd") { 94 | console.log(obj); 95 | if (obj.text[0] === "”") { 96 | cm.replaceRange( 97 | "“”", 98 | { line: cursorInfo.line, ch: cursorInfo.ch - 1 }, 99 | { line: cursorInfo.line, ch: cursorInfo.ch }, 100 | "*composeSymbol222" 101 | ); 102 | cm.setCursor({ line: cursorInfo.line, ch: cursorInfo.ch }); 103 | } 104 | if (obj.text[0] === "’") { 105 | cm.replaceRange( 106 | "‘", 107 | { line: cursorInfo.line, ch: cursorInfo.ch - 1 }, 108 | { line: cursorInfo.line, ch: cursorInfo.ch }, 109 | "*composeSymbol" 110 | ); 111 | cm.setCursor({ line: cursorInfo.line, ch: cursorInfo.ch }); 112 | } 113 | } 114 | }; 115 | beforeChange = (cm: CodeMirror.Editor, obj: CodeMirror.EditorChange) => { 116 | const allowSelectEmbed = this.settings.allowSelectEmbed; 117 | 118 | const symbol = obj.text[0]; 119 | const pair = this.pairs[symbol]; 120 | if (pair === undefined) { 121 | return; 122 | } 123 | 124 | if (allowSelectEmbed && cm.somethingSelected()) { 125 | const selected = cm.getSelection(); 126 | const replaceText = `${symbol}${selected}${pair}`; 127 | 128 | // @ts-ignore 129 | obj.update(null, null, [replaceText]); 130 | } 131 | }; 132 | 133 | onunload() { 134 | this.app.workspace.iterateCodeMirrors(cm => { 135 | cm.off("beforeChange", this.beforeChange); 136 | }); 137 | this.app.workspace.iterateCodeMirrors(cm => { 138 | cm.off("change", this.change); 139 | }); 140 | } 141 | 142 | async loadSettings() { 143 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 144 | } 145 | 146 | async saveSettings() { 147 | await this.saveData(this.settings); 148 | } 149 | } 150 | 151 | class SampleSettingTab extends PluginSettingTab { 152 | plugin: AutoPairPlugin; 153 | 154 | constructor(app: App, plugin: AutoPairPlugin) { 155 | super(app, plugin); 156 | this.plugin = plugin; 157 | } 158 | 159 | display(): void { 160 | let { containerEl } = this; 161 | 162 | containerEl.empty(); 163 | 164 | containerEl.createEl("h2", { text: "中文符号自动补齐" }); 165 | 166 | new Setting(containerEl) 167 | .setName("允许选中文字后在两边插入符号") 168 | .addToggle(text => 169 | text 170 | .setValue(this.plugin.settings.allowSelectEmbed) 171 | .onChange(async value => { 172 | this.plugin.settings.allowSelectEmbed = value; 173 | await this.plugin.saveSettings(); 174 | }) 175 | ); 176 | 177 | new Setting(containerEl) 178 | .setName("允许对下单引号和下双引号向前自动补齐") 179 | .addToggle(text => 180 | text.setValue(this.plugin.settings.allowQuote).onChange(async value => { 181 | this.plugin.settings.allowQuote = value; 182 | await this.plugin.saveSettings(); 183 | }) 184 | ); 185 | } 186 | } 187 | --------------------------------------------------------------------------------