├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── README.md ├── README_CN.md ├── README_EN.md ├── main.js ├── manifest.json └── styles.css /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | tags: 9 | - "*" # Push events to matching v*, i.e. v1.0, v20.15.10 10 | branches: 11 | - main 12 | path-ignores: 13 | - README*.md 14 | 15 | jobs: 16 | build-and-test: 17 | if: startsWith(github.ref, 'refs/tags/') 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest] 21 | runs-on: ${{ matrix.os }} 22 | permissions: 23 | contents: write 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | - name: Get package version 29 | run: | 30 | node -p -e '`PLUGIN_ID=${require("./manifest.json").id}`' >> $GITHUB_ENV 31 | node -p -e '`MANIFEST_VERSION=${require("./manifest.json").version}`' >> $GITHUB_ENV 32 | 33 | - name: zip build 34 | run: zip ${{ env.PLUGIN_ID }}-${{ env.MANIFEST_VERSION }}.zip styles.css main.js manifest.json 35 | 36 | - name: Create Release 37 | uses: softprops/action-gh-release@v2 38 | with: 39 | files: | 40 | styles.css 41 | main.js 42 | manifest.json 43 | ${{ env.PLUGIN_ID }}-${{ env.MANIFEST_VERSION }}.zip 44 | token: ${{ secrets.gh_token }} 45 | name: Release ${{ env.MANIFEST_VERSION }} 46 | make_latest: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | # main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Callout Editor for Obsidian 2 | [English](README_EN.md) | 中文 3 | 4 | Callout Editor是一款Obsidian编辑插件,它通过卡片的自由创建组织,所见即所得的编辑方式,实时色彩选择以及灵活的瀑布流布局,优化了Callout的实时编辑与呈现形式。 5 | 6 | # 1.0.2 版本更新说明 7 | 8 | 很高兴地宣布 Callout Editor 1.0.2 版本发布!这次更新主要聚焦于性能优化和用户体验提升: 9 | 10 | ## 🚀 主要更新 11 | - **显著性能提升**:重构了实时保存机制,大幅减少编辑时的卡顿现象 12 | - **实时保存**:实现了内容的实时保存,可以在输入完毕等待1.5秒后自动保存 13 | - **更流畅的编辑体验**:优化了防抖处理,提供更自然的编辑响应 14 | 15 | ## 功能特点 16 | 17 | - **所见即所得编辑**:在Obsidian中提供Callout的实时编辑功能,简化卡片的创建与修改过程。 18 | - **瀑布流布局**:支持一键创建不同列数的卡片布局,以清晰、有序的方式展示笔记内容。 19 | - **颜色选择器**:通过每个卡片右下角的颜色选择器,轻松为卡片添加色彩标记,增强信息分类的直观性。 20 | - **实时内容储存**:通过在当前卡片文本框外的任何地方点击,即可确保您的修改被立即且自动保存。 21 | 22 | ## 安装步骤 23 | 24 | 请参考: [How to install Obsidian Plugins](https://forum.obsidian.md/t/plugins-mini-faq/7737) 25 | 26 | 1. 创建文件夹 `obsidian-callout-editor` 27 | 2. 下载 `main.js, manifest.json, styles.css` 28 | 3. 将下载文件放入文件夹 29 | 4. 将文件夹放入 YourVault/.obsidian/plugins 30 | 5. 重启Obsidian 31 | 32 | ## 使用指南 33 | 34 | ### 插入瀑布流卡片簇 35 | 36 | - 使用`Ctrl/Cmd + P`呼出命令面板。 37 | - 搜索并选择`Callout Editor: insert two/three/four/five columns`,即可根据需要插入卡片簇。 38 | 39 | ### 快速调整颜色 40 | 41 | - 将鼠标悬停在卡片的右下角以显示颜色选择器。 42 | - 选择适当的颜色,为卡片或文本进行视觉分类。 43 | 44 | ### 添加新卡片 45 | 46 | - 点击卡片右侧的添加按钮,即可在当前卡片旁边新增卡片。 47 | 48 | ### 保存更改 49 | 50 | - 在当前编辑区域外点击一下,即可自动保存所有更改。 51 | 52 | ## 开源贡献 53 | 54 | 欢迎任何形式的贡献,无论是功能建议、问题报告还是代码提交。请访问[GitHub仓库](https://github.com/wnhllh/obsidian-callout-editor)。 55 | 56 | ## 更新日志 57 | 58 | ### 1.0.2 (2024-04-09) 59 | - 🚀 大幅优化实时保存机制,显著减少编辑时的卡顿现象 60 | - ⚡️ 重构代码提升编辑性能 61 | - 🐛 修复中文输入法输入时的实时保存问题 62 | - 🔧 优化防抖处理,提供更流畅的编辑体验 63 | 64 | ### 1.0.1 (2024-04-08) 65 | - 🔧 优化实时保存机制,减少卡顿现象 66 | - ⚡️ 提升编辑性能 67 | - 🐛 修复输入法输入时的保存问题 68 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Callout Editor for Obsidian 2 | [English](README_EN.md) | 中文 3 | 4 | Callout Editor是一款Obsidian编辑插件,它通过卡片的自由创建组织,所见即所得的编辑方式,实时色彩选择以及灵活的瀑布流布局,优化了Callout的实时编辑与呈现形式。 5 | 6 | ## 功能特点 7 | 8 | - **所见即所得编辑**:在Obsidian中提供Callout的实时编辑功能,简化卡片的创建与修改过程。 9 | - **瀑布流布局**:支持一键创建不同列数的卡片布局,以清晰、有序的方式展示笔记内容。 10 | - **颜色选择器**:通过每个卡片右下角的颜色选择器,轻松为卡片添加色彩标记,增强信息分类的直观性。 11 | - **实时内容储存**:通过在当前卡片文本框外的任何地方点击,即可确保您的修改被立即且自动保存。 12 | 13 | ## 安装步骤 14 | 15 | 请参考: [How to install Obsidian Plugins](https://forum.obsidian.md/t/plugins-mini-faq/7737) 16 | 17 | 1. 创建文件夹 `obsidian-callout-editor` 18 | 2. 下载 `main.js, manifest.json, styles.css` 19 | 3. 将下载文件放入文件夹 20 | 4. 将文件夹放入 YourVault/.obsidian/plugins 21 | 5. 重启Obsidian 22 | 23 | ## 使用指南 24 | 25 | ### 插入瀑布流卡片簇 26 | 27 | - 使用`Ctrl/Cmd + P`呼出命令面板。 28 | - 搜索并选择`Callout Editor: insert two/three/four/five columns`,即可根据需要插入卡片簇。 29 | 30 | ### 快速调整颜色 31 | 32 | - 将鼠标悬停在卡片的右下角以显示颜色选择器。 33 | - 选择适当的颜色,为卡片或文本进行视觉分类。 34 | 35 | ### 添加新卡片 36 | 37 | - 点击卡片右侧的添加按钮,即可在当前卡片旁边新增卡片。 38 | 39 | ### 保存更改 40 | 41 | - 在当前编辑区域外点击一下,即可自动保存所有更改。 42 | 43 | ## 开源贡献 44 | 45 | 欢迎任何形式的贡献,无论是功能建议、问题报告还是代码提交。请访问[GitHub仓库](https://github.com/wnhllh/obsidian-callout-editor)。 46 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Callout Editor for Obsidian 2 | 3 | English | [中文](README_CN.md) 4 | 5 | Elevate your note-taking experience in Obsidian with the Callout Editor plugin. Designed to enrich your notes with a visually organized structure, the Callout Editor seamlessly integrates advanced callout functionalities directly into Obsidian's command palette. Craft your notes with precision and ease, bringing clarity and efficiency to your workflow. 6 | 7 | ## Features 8 | 9 | - **Dynamic Callout Columns**: Instantly insert two, three, four, or five columns within your notes through Obsidian's command palette (`Ctrl/Cmd + P`), enhancing the layout and readability of your content. 10 | - **Real-time Color Customization**: Hover over the bottom-right corner of any card to summon the color selector, offering a palette of colors to categorize and personalize your notes visually. 11 | - **Seamless Content Expansion**: With a simple click on the add button located at the right side of your current card, effortlessly introduce a new card to your cascading layout, enabling fluid content addition without breaking your flow. 12 | - **Intuitive Content Saving**: Secure your modifications by clicking anywhere outside the current callout text box, ensuring your changes are saved instantly and automatically. 13 | 14 | ## Installation 15 | 16 | Please Refer: [How to install Obsidian Plugins](https://forum.obsidian.md/t/plugins-mini-faq/7737) 17 | 18 | 1. Create a folder `obsidian-callout-editor` 19 | 2. Download `main.js, manifest.json, styles.css` 20 | 3. Put the downloaded files into the folder 21 | 4. Put the folder in YourVault/.obsidian/plugins 22 | 5. Reload the plugins in Obsidian settings or restart Obsidian 23 | 24 | ## Usage 25 | 26 | ### Inserting Columns 27 | 28 | - Activate Obsidian's command palette with `Ctrl/Cmd + P`. 29 | - Type and select `Callout Editor: insert two columns` (or three, four, five as per your need) to structure your note instantly. 30 | 31 | ### Changing Colors 32 | 33 | - Hover over the bottom-right corner of a callout card to display the color selector. 34 | - Select your desired color to apply to the card, categorizing your content visually. 35 | 36 | ### Adding New Cards 37 | 38 | - Click the add button on the right side of a card to add a new card directly beside it, facilitating seamless content expansion. 39 | 40 | ### Saving Changes 41 | 42 | - Click anywhere outside the current editing callout to save your changes automatically. 43 | 44 | ## Contributing 45 | 46 | Contributions to the Callout Editor are warmly welcomed. Whether it's feature requests, bug reports, or code contributions, feel free to reach out or submit a pull request on [GitHub](https://github.com/wnhllh/obsidian-callout-editor). 47 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 3 | if you want to view the source, please visit the github repository of this plugin 4 | */ 5 | 6 | var __defProp = Object.defineProperty; 7 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 8 | var __getOwnPropNames = Object.getOwnPropertyNames; 9 | var __hasOwnProp = Object.prototype.hasOwnProperty; 10 | var __export = (target, all) => { 11 | for (var name in all) 12 | __defProp(target, name, { get: all[name], enumerable: true }); 13 | }; 14 | var __copyProps = (to, from, except, desc) => { 15 | if ((from && typeof from === "object") || typeof from === "function") { 16 | for (let key of __getOwnPropNames(from)) 17 | if (!__hasOwnProp.call(to, key) && key !== except) 18 | __defProp(to, key, { 19 | get: () => from[key], 20 | enumerable: 21 | !(desc = __getOwnPropDesc(from, key)) || 22 | desc.enumerable, 23 | }); 24 | } 25 | return to; 26 | }; 27 | var __toCommonJS = (mod) => 28 | __copyProps(__defProp({}, "__esModule", { value: true }), mod); 29 | 30 | // main.ts 31 | var main_exports = {}; 32 | __export(main_exports, { 33 | default: () => CalloutPlugin, 34 | }); 35 | module.exports = __toCommonJS(main_exports); 36 | var import_obsidian6 = require("obsidian"); 37 | 38 | // src/global.ts 39 | var numLines = 4; 40 | var vacantLines = 0; 41 | // var vacantList = [] 42 | var numCols = []; 43 | var contentList = []; 44 | var onEditingClass = "on-editing"; 45 | var onEditingMulti = "multi-editing"; 46 | 47 | // function getCaretPosition(editableElem) { 48 | // let caretPos = 0, 49 | // sel, 50 | // range 51 | // sel = activeWindow.getSelection() 52 | // if (sel && sel.rangeCount) { 53 | // range = sel.getRangeAt(0) 54 | // caretPos = range.endOffset 55 | // // if (range.commonAncestorContainer.parentNode == editableElem) { 56 | // // caretPos = range.endOffset; 57 | // // console.log("caretPos=", caretPos); 58 | // // } 59 | // } 60 | // return caretPos 61 | // } 62 | 63 | // function setCaretPosition(editableElem, newPos) { 64 | // let caretPos = 0, 65 | // sel, 66 | // range = activeDocument.createRange() 67 | // sel = activeWindow.getSelection() 68 | // if (sel && sel.rangeCount) { 69 | // // range.setStart(editableElem.childNodes[0], newPos); 70 | // range.setStart(editableElem.childNodes[1], newPos) 71 | // range.collapse(true) 72 | // sel.removeAllRanges() 73 | // sel.addRange(range) 74 | // } 75 | // return caretPos 76 | // } 77 | 78 | function HTMLtoList0(rawContent) { 79 | let contentTemp = []; 80 | let n = rawContent.length; 81 | let i = 0; 82 | while (n > 0) { 83 | if (!rawContent[i].startsWith("")) { 85 | rawContent[i] = rawContent[i].replace("

", ""); 86 | } 87 | if (rawContent[i].startsWith("

")) { 88 | rawContent[i] = rawContent[i].replace("

", ""); 89 | } 90 | if (rawContent[i].endsWith("
")) { 91 | // * 多个空行 92 | rawContent[i] = rawContent[i].replace(/
$/, ""); 93 | } 94 | if (rawContent[i].contains("
")) { 95 | let temp = rawContent[i].split("
"); 96 | n = n + temp.length - 1; 97 | 98 | for (let j = 0; j < temp.length; j++) { 99 | rawContent.splice(i + j + 1, 0, temp[j]); 100 | } 101 | rawContent.splice(i, 1); 102 | } 103 | 104 | if (rawContent[i].match(//g)) { 105 | const imgMatches = rawContent[i].match( 106 | //g 107 | ); 108 | // 如果找到匹配项,则对每个匹配项进行替换 109 | if (imgMatches) { 110 | imgMatches.forEach((imgTag) => { 111 | // 将 标签转换为 Markdown 格式 112 | const imgUrl = imgTag.match(/\"(.*?)\"/)[1]; 113 | const markdownImg = "![](" + imgUrl + ")"; 114 | 115 | // 替换原内容中的 标签 116 | rawContent[i] = rawContent[i].replace( 117 | imgTag, 118 | markdownImg 119 | ); 120 | }); 121 | } 122 | } 123 | 124 | // if (rawContent[i].startsWith('(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\(.*?)\ 0){ 154 | // contentTemp.push('') 155 | // k = 0; 156 | // } 157 | // } 158 | if (rawContent[i] !== "") { 159 | contentTemp.push(rawContent[i]); 160 | } else { 161 | if (i !== 0 && i !== rawContent.length - 1) { 162 | vacantLines++; 163 | } 164 | } 165 | } 166 | n--; 167 | i++; 168 | } 169 | 170 | return contentTemp; 171 | } 172 | 173 | function HTMLtoList2(rawContent) { 174 | function processContentOptimized(htmlSegments) { 175 | // 将所有HTML片段合并为一个字符串 176 | // const fullHtml = htmlSegments.join('') 177 | const fullHtml = htmlSegments 178 | .map((segment) => 179 | segment.endsWith("
") ? segment : segment + "
" 180 | ) 181 | .join(""); 182 | 183 | let filteredHtml = fullHtml.replace( 184 | /

]*>[\s\S]*?<\/div>/g, 185 | "" 186 | ); 187 | 188 | // 针对
元素及其全部内容的替换逻辑 189 | let processedHtml = filteredHtml 190 | .replace(/<\/p>\s*

/g, "
") 191 | .replace( 192 | /

]*>([\s\S]*?)<\/div><\/div>/g, 193 | "\n[PLACEABLE]\n" 194 | ) 195 | .replace(/]*>/g, "![]($1)") 196 | .replace(/(.*?)<\/strong>/g, "**$1**") 197 | .replace(/(.*?)<\/em>/g, "*$1*") 198 | .replace(/(.*?)<\/del>/g, "~~$1~~") 199 | .replace(/]*>(.*?)<\/h\1>/g, (match, level, content) => { 200 | return `${"#".repeat( 201 | parseInt(level, 10) 202 | )} ${content.trim()}\n\n`; 203 | }) 204 | .replace( 205 | /
    ([\s\S]*?)<\/ul>/g, 206 | (match, listItems) => { 207 | // 未选中的任务列表项 208 | let uncheckedItems = listItems 209 | .replace( 210 | /
  • ]*>(.*?)<\/li>/g, 211 | "- [ ] $1\n" 212 | ) 213 | .trim(); 214 | // 选中的任务列表项 215 | let checkedItems = uncheckedItems 216 | .replace( 217 | /
  • ]*checked=""[^>]*>(.*?)<\/li>/g, 218 | "- [x] $1\n" 219 | ) 220 | .trim(); 221 | return `\n${checkedItems}\n`; 222 | } 223 | ) 224 | 225 | // 新增:处理无序列表 226 | .replace(/
      ([\s\S]*?)<\/ul>/g, (match, listItems) => { 227 | return listItems.replace(/
    • (.*?)<\/li>/g, "- $1\n").trim(); 228 | }) 229 | // 新增:处理有序列表 230 | .replace(/
        ([\s\S]*?)<\/ol>/g, (match, listItems) => { 231 | let counter = 1; 232 | return listItems 233 | .replace(/
      1. (.*?)<\/li>/g, () => `${counter++}. $1\n`) 234 | .trim(); 235 | }) 236 | 237 | .replace( 238 | /
        (.*?)<\/code>