├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── configuration-interface-v0_0_2.png ├── configuration-interface.png └── quick-start.gif ├── esbuild.config.mjs ├── manifest.json ├── package.json ├── src ├── main.ts ├── serialNumberHelper.ts └── settings.ts ├── styles.css ├── tsconfig.json └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "parserOptions": { 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-vars": "off", 17 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 18 | "@typescript-eslint/ban-ts-comment": "off", 19 | "no-prototype-builtins": "off", 20 | "@typescript-eslint/no-empty-function": "off" 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | package-lock.json 11 | 12 | # Don't include the compiled main.js file in the repo. 13 | # They should be uploaded to GitHub releases instead. 14 | main.js 15 | 16 | # Exclude sourcemaps 17 | *.map 18 | 19 | # obsidian 20 | data.json 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 deming yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Title Serial Number Plugin 2 | 3 | This plugin adds serial numbers to your markdown title. 4 | 5 | ![quick start](https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/master/assets/quick-start.gif) 6 | 7 | Now, This plugin noly provides two commands and their graphical interface for configuration! 8 | 9 | - commands: 10 | - `Set Serial Number For Title` 11 | - `Clear Serial Number For Title` 12 | - configuration interface: 13 | 14 | ![configuration interface](https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/master/assets/configuration-interface-v0_0_2.png) 15 | 16 | ## About configration 17 | 18 | In version v0.0.2, we changed configration mode. You can active any headline you want, and other headline in you aritcle which not active will not generate serial number. 19 | 20 | For example, I acitved H2 and H4. 21 | 22 | ```markdown 23 | ## Hello 24 | 25 | #### World 26 | 27 | #### Have 28 | 29 | ## A 30 | 31 | #### Good 32 | 33 | #### Day 34 | ``` 35 | 36 | When I executed command `Set Serial Number For Title` in above article, I got this: 37 | 38 | ```markdown 39 | ## 1 Hello 40 | 41 | #### 1.1 World 42 | 43 | #### 1.2 Have 44 | 45 | ## 2 A 46 | 47 | #### 2.1 Good 48 | 49 | #### 2.2 Day 50 | ``` -------------------------------------------------------------------------------- /assets/configuration-interface-v0_0_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/e325dc66f4132ced7e8a0f4b4b948ac93eaa24a4/assets/configuration-interface-v0_0_2.png -------------------------------------------------------------------------------- /assets/configuration-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/e325dc66f4132ced7e8a0f4b4b948ac93eaa24a4/assets/configuration-interface.png -------------------------------------------------------------------------------- /assets/quick-start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/e325dc66f4132ced7e8a0f4b4b948ac93eaa24a4/assets/quick-start.gif -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['src/main.ts'], 19 | bundle: true, 20 | external: ['obsidian', 'electron', ...builtins], 21 | format: 'cjs', 22 | watch: !prod, 23 | target: 'es2016', 24 | logLevel: "info", 25 | sourcemap: prod ? false : 'inline', 26 | treeShaking: true, 27 | outfile: 'main.js', 28 | }).catch(() => process.exit(1)); 29 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-title-serial-number-plugin", 3 | "name": "Title Serial Number Plugin", 4 | "version": "0.0.2", 5 | "minAppVersion": "0.12.0", 6 | "description": "This plugin adds serial numbers to your markdown title.", 7 | "author": "Domenic", 8 | "authorUrl": "", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "0.12.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "node esbuild.config.mjs production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/node": "^16.11.6", 15 | "@typescript-eslint/eslint-plugin": "^5.2.0", 16 | "@typescript-eslint/parser": "^5.2.0", 17 | "builtin-modules": "^3.2.0", 18 | "esbuild": "0.13.12", 19 | "obsidian": "^0.12.17", 20 | "tslib": "2.3.1", 21 | "typescript": "4.4.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; 2 | import { SerialNumberHelper } from './serialNumberHelper'; 3 | import { TitleSerialNumberPluginSettings, TitleSerialNumberPluginSettingTab } from './settings'; 4 | 5 | 6 | 7 | 8 | const DEFAULT_SETTINGS: TitleSerialNumberPluginSettings = { 9 | activedHeadlines: [1, 2, 3, 4, 5, 6] 10 | } 11 | 12 | export default class TitleSerialNumberPlugin extends Plugin { 13 | settings: TitleSerialNumberPluginSettings; 14 | 15 | async onload() { 16 | await this.loadSettings(); 17 | 18 | // This adds an editor command that can perform some operation on the current editor instance 19 | this.addCommand({ 20 | id: 'set-title-serial-number-editor-command', 21 | name: 'Set Serial Number For Title', 22 | editorCallback: (editor: Editor, view: MarkdownView) => { 23 | //console.log(this.settings); 24 | let startWith: number = 1//parseInt(this.settings.startWith); 25 | let endWith: number = 3//parseInt(this.settings.endWith); 26 | if(startWith > endWith){ 27 | new Notice('Your configuration is ERROR, command terminated!'); 28 | return; 29 | } 30 | //startWith == endWith时,仅仅对这一数组等级的进行标题添加操作 31 | endWith += 1;//为了写程序方便,把结束自增1 32 | 33 | const regex = /^([#]+) ([0-9.]* *)(.*)$/gm; 34 | let originVal: string = editor.getValue(); 35 | let m; 36 | while ((m = regex.exec(originVal)) !== null) { 37 | if (m.index === regex.lastIndex) { 38 | regex.lastIndex++; 39 | } 40 | //js 返回的匹配结果:groupIndex=0表示匹配到的字符串、groupIndex=1表示匹配到的字符串的第一个分组的值、groupIndex=2表示匹配到的字符串的第二个分组的值 41 | let str: string = m[0];//当前匹配到的完整字符串 42 | let wellStr:string = m[1];//井号的字符串;用于判定是h1还是h2还是……h6 43 | let oldSerialNumber: string = m[2];//旧的标题号(如果有);如1.1, 1. , 2.2.1,…… 44 | let title:string = m[3]; 45 | let matchStartIndex:number = m.index;//匹配项在文本中的索引位置 46 | //获取序号 47 | let newSerialNumber = SerialNumberHelper.getSerialNumberStr(wellStr.length, this.settings.activedHeadlines);//新的序号 48 | let result = ''; 49 | if (newSerialNumber === '') { 50 | result = `${wellStr} ${title}`; 51 | } 52 | else{ 53 | result = `${wellStr} ${newSerialNumber} ${title}`; 54 | } 55 | let sub1 = originVal.substring(0, matchStartIndex); 56 | let sub2 = originVal.substring(matchStartIndex + str.length); 57 | originVal = sub1 + '' + result + sub2; 58 | } 59 | editor.setValue(originVal); 60 | SerialNumberHelper.resetHxSerialNumbers(0);//全部重置为0; 61 | } 62 | }); 63 | 64 | this.addCommand({ 65 | id: 'clear-title-serial-number-editor-command', 66 | name: 'Clear Serial Number For Title', 67 | editorCallback: (editor: Editor, view: MarkdownView) => { 68 | 69 | const regex2 = /^([#]+) ([0-9.]* *)(.*)$/gm; 70 | let originVal: string = editor.getValue(); 71 | let m; 72 | while ((m = regex2.exec(originVal)) !== null) { 73 | if (m.index === regex2.lastIndex) { 74 | regex2.lastIndex++; 75 | } 76 | console.log(m); 77 | //js 返回的匹配结果:groupIndex=0表示匹配到的字符串、groupIndex=1表示匹配到的字符串的第一个分组的值、groupIndex=2表示匹配到的字符串的第二个分组的值 78 | let str: string = m[0];//当前匹配到的完整字符串 79 | let wellStr:string = m[1];//井号的字符串;用于判定是h1还是h2还是……h6 80 | let oldSerialNumber: string = m[2];//旧的标题号(如果有);如1.1, 1. , 2.2.1,…… 81 | let title:string = m[3]; 82 | let matchStartIndex:number = m.index;//匹配项在文本中的索引位置 83 | 84 | let result = `${wellStr} ${title}`; 85 | let sub1 = originVal.substring(0, matchStartIndex); 86 | let sub2 = originVal.substring(matchStartIndex + str.length); 87 | originVal = sub1 + '' + result + sub2; 88 | } 89 | editor.setValue(originVal); 90 | SerialNumberHelper.resetHxSerialNumbers(0);//全部重置为0; 91 | } 92 | }); 93 | 94 | // This adds a settings tab so the user can configure various aspects of the plugin 95 | this.addSettingTab(new TitleSerialNumberPluginSettingTab(this.app, this)); 96 | } 97 | 98 | onunload() { 99 | 100 | } 101 | 102 | async loadSettings() { 103 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 104 | } 105 | 106 | async saveSettings() { 107 | await this.saveData(this.settings); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/serialNumberHelper.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * 序号类 5 | */ 6 | export class SerialNumberHelper{ 7 | /** Hx的序号 */ 8 | private static hxSerialNumbers: number[] = [0, 0, 0, 0, 0, 0]; 9 | 10 | /** 11 | * 重置区间为[idx,6]的序号为0 12 | * @param idx 若idx=2(索引从0开始,故表示当前为h3),则把h3、h4、h5、h6的序号重置为0 13 | */ 14 | public static resetHxSerialNumbers(idx: number){ 15 | for (let index = idx; index < 6; index++) { 16 | SerialNumberHelper.hxSerialNumbers[index] = 0; 17 | } 18 | } 19 | 20 | /** 21 | * 获取序号 22 | * @param level 当前标题等级;如h4,则level=4 (即文章中这样写的: `#### 标题`) 23 | * @param startWith 取值范围:1~6;从h几开始添加标题,如2,则#不生成序号,##生成序号1、2、3,###生成序号1.1、1.2、1.3…… 24 | * @param endWith 取值范围 `1~7`;如2,则只对h1生成序号;如7,则对h1~6生成序号 25 | * @returns 点分序号 或 空字符串 26 | */ 27 | public static getSerialNumberStr(level: number, activedHeadlines: number[]): string{ 28 | if (!activedHeadlines.includes(level)) { 29 | return '';//不在激活列表的标题, 不会生成序号 30 | } 31 | 32 | let serialLevel: number = activedHeadlines.indexOf(level) + 1;//序号等级, 如激活列表activedHeadlines=[3,4,6], 则[### 标题]会被转换为[### 1 标题], 此时h3对应的序号等级应该是1, 也就是说, 真实生成的序号应该只有一位数 33 | 34 | let newSerialNumber: string = '';//新的序号 35 | for (let idx = 0; idx < serialLevel; idx++) { 36 | if (idx == serialLevel - 1) { 37 | SerialNumberHelper.resetHxSerialNumbers(idx + 1);//若当前是h3,则把h4、h5、h6的索引值清空为0; 38 | SerialNumberHelper.hxSerialNumbers[idx] += 1; 39 | newSerialNumber += `${SerialNumberHelper.hxSerialNumbers[idx]}`; 40 | } 41 | else{ 42 | newSerialNumber += `${SerialNumberHelper.hxSerialNumbers[idx]}.`; 43 | } 44 | } 45 | return `${newSerialNumber}`; 46 | } 47 | } -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from 'obsidian'; 2 | import TitleSerialNumberPlugin from './main'; 3 | 4 | 5 | export interface TitleSerialNumberPluginSettings { 6 | activedHeadlines: number[];//激活的标题;(如存放1,3,5), 则[# 标题]会被转换为: [# 1 标题], [##### 测试] 会被转换为: [##### 1.1.1 测试] 7 | } 8 | 9 | export class TitleSerialNumberPluginSettingTab extends PluginSettingTab { 10 | plugin: TitleSerialNumberPlugin; 11 | 12 | constructor(app: App, plugin: TitleSerialNumberPlugin) { 13 | super(app, plugin); 14 | this.plugin = plugin; 15 | } 16 | 17 | display(): void { 18 | const {containerEl} = this; 19 | 20 | containerEl.empty(); 21 | 22 | containerEl.createEl('h2', {text: 'Settings for title serial number plugin.'}); 23 | 24 | containerEl.createDiv({text: "Which headlines do you like to generate serial number for?"}); 25 | 26 | let div = containerEl.createDiv(); 27 | for (const i of [1, 2, 3, 4, 5, 6]) { 28 | div.createEl('label', { attr: { for: `h${i}` }, text: `H${i}` }); 29 | 30 | let hxInput = div.createEl('input', { type: 'checkbox', attr: {id: `h${i}`, name: "actived-headline"}}); 31 | if(this.plugin.settings.activedHeadlines.includes(i)){ 32 | hxInput.checked = true//把从文件读取出来的用户设置, 还原到复选框上 33 | } 34 | 35 | let activedArr: number[] = [] 36 | hxInput.addEventListener("change", async (ev)=>{ 37 | let cked = div.querySelectorAll("input[type=checkbox][name=actived-headline]"); 38 | for (let i = 0; i < cked.length; i++) { 39 | const element = cked[i]; 40 | let e = element; 41 | //console.log(`h${i+1}: ${e.checked}`) 42 | 43 | if(e.checked){ 44 | activedArr.push(i + 1) 45 | } 46 | this.plugin.settings.activedHeadlines = activedArr; 47 | await this.plugin.saveSettings(); 48 | } 49 | 50 | }); 51 | 52 | div.createEl("span", {text: " | "});//分割 53 | } 54 | 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yalvhe2009/obsidian-title-serial-number-plugin/e325dc66f4132ced7e8a0f4b4b948ac93eaa24a4/styles.css -------------------------------------------------------------------------------- /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 | "ES6", 16 | "ES7" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.1": "0.9.12", 3 | "1.0.0": "0.9.7" 4 | } 5 | --------------------------------------------------------------------------------