├── styles.css ├── .eslintignore ├── assets └── demo-sembr.gif ├── .gitignore ├── .editorconfig ├── .github ├── FUNDING.yml ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml └── workflows │ └── release.yml ├── versions.json ├── manifest.json ├── tsconfig.json ├── package.json ├── esbuild.config.mjs ├── Changelog.md ├── LICENSE ├── .release.sh ├── .eslintrc ├── main.ts └── README.md /styles.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /assets/demo-sembr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisgrieser/obsidian-sembr/HEAD/assets/demo-sembr.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | package-lock.json 4 | 5 | # obsidian 6 | data.json 7 | main.js 8 | 9 | # Mac 10 | .DS_Store 11 | 12 | # Info for me 13 | Info for me.md 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/displaying-a-sponsor-button-in-your-repository 2 | 3 | custom: https://www.paypal.me/ChrisGrieser 4 | ko_fi: pseudometa 5 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1": "0.13.19", 3 | "0.5.0": "0.13.19", 4 | "0.5.1": "0.13.19", 5 | "0.5.2": "0.13.19", 6 | "0.5.3": "0.13.19", 7 | "0.5.4": "0.13.19", 8 | "0.5.5": "0.13.19", 9 | "0.5.6": "0.13.19", 10 | "0.5.7": "0.13.19", 11 | "0.6.0": "0.13.19", 12 | "0.7.0": "0.13.19" 13 | } 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of the Change 2 | 3 | 4 | ## Checklist 5 | - [ ] Used the provided eslint configuration, [as explained in the Readme](README.md#Contribute). 6 | - [ ] Did *not* use prettier. 7 | - [ ] Used expressive variable names. 8 | - [ ] Code is commented well. 9 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-sembr", 3 | "name": "Semantic Line Breaker", 4 | "version": "0.7.0", 5 | "minAppVersion": "0.13.19", 6 | "description": "Apply and remove Semantic Line Breaks (SemBr).", 7 | "author": "pseudometa", 8 | "authorUrl": "https://chris-grieser.de/", 9 | "isDesktopOnly": false 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": [ 13 | "DOM", 14 | "ES5", 15 | "ES6", 16 | "ES7" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{plugin-id}}", 3 | "version": "0.7.0", 4 | "description": "{{plugin-desc}}", 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 | -------------------------------------------------------------------------------- /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: ['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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea 3 | title: "Feature Request: " 4 | labels: ["feature request"] 5 | body: 6 | - type: textarea 7 | id: feature-requested 8 | attributes: 9 | label: Feature Requested 10 | description: A clear and concise description of the feature. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshot 15 | attributes: 16 | label: Relevant Screenshot 17 | description: If applicable, add screenshots or a screen recording to help explain the request. 18 | - type: checkboxes 19 | id: checklist 20 | attributes: 21 | label: Checklist 22 | options: 23 | - label: The feature would be useful to more users than just me. 24 | required: true 25 | 26 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | - 2022-06-07 release 0.7.0 2 | - 2022-06-07 prevent splitting footnotes & pandoc citations 3 | - 2022-06-01 release 0.6.0 4 | - 2022-06-01 ignore (fenced) code blocks 5 | - 2022-05-20 update promo screenshot 6 | - 2022-05-20 release 0.5.7 7 | - 2022-05-20 fix deconverting sembr merging blockquotes and lists (#5) 8 | - 2022-05-20 fix #6 9 | - 2022-05-20 release 0.5.6 10 | - 2022-05-20 release 0.5.5 11 | - 2022-05-20 add minimum line length of 10 chars before sembr applies 12 | - 2022-05-20 release 0.5.4 13 | - 2022-05-20 bugfix 14 | - 2022-05-20 release 0.5.3 15 | - 2022-05-20 release 0.5.2 16 | - 2022-05-20 deconvert from sembr even without trailing whitespace (#4) 17 | - 2022-05-19 release 0.5.1 18 | - 2022-05-19 fix #2 19 | - 2022-05-19 fix #1 20 | - 2022-05-19 release 0.5.0 21 | - 2022-05-19 first prototype 22 | - 2022-05-19 description of sembr 23 | - 2022-05-19 filled out basic info 24 | - 2022-05-19 Initial commit 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christopher Grieser 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: textarea 7 | id: bug-description 8 | attributes: 9 | label: Bug Description 10 | description: A clear and concise description of the bug. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshot 15 | attributes: 16 | label: Relevant Screenshot 17 | description: If applicable, add screenshots or a screen recording to help explain your problem. 18 | - type: textarea 19 | id: reproduction-steps 20 | attributes: 21 | label: To Reproduce 22 | description: Steps to reproduce the problem 23 | placeholder: | 24 | For example: 25 | 1. Go to '...' 26 | 2. Click on '...' 27 | 3. Scroll down to '...' 28 | - type: input 29 | id: obsi-version 30 | attributes: 31 | label: Obsidian Version 32 | description: You can find the version in the *About* Tab of the settings. 33 | placeholder: 0.13.19 34 | validations: 35 | required: true 36 | - type: checkboxes 37 | id: editor 38 | attributes: 39 | label: Which editor are you using? 40 | options: 41 | - label: New Editor 42 | - label: Legacy Editor 43 | - type: checkboxes 44 | id: checklist 45 | attributes: 46 | label: Checklist 47 | options: 48 | - label: I updated to the latest version of the plugin. 49 | required: true 50 | 51 | -------------------------------------------------------------------------------- /.release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Release Obsidian Plugin 4 | # https://forum.obsidian.md/t/using-github-actions-to-release-plugins/7877 5 | # https://marcus.se.net/obsidian-plugin-docs/publishing/release-your-plugin-with-github-actions 6 | 7 | # Requirements 8 | # - markdownlint 9 | # - eslint 10 | 11 | # ensure relevant files exist 12 | if [[ ! -f "./manifest.json" ]] ; then 13 | echo "manifest.json does not exist yet" 14 | exit 1 15 | fi 16 | if [[ ! -f "./versions.json" ]] ; then 17 | echo "versions.json does not exist yet" 18 | exit 1 19 | fi 20 | if [[ ! -f "./package.json" ]] ; then 21 | echo "package.json does not exist yet" 22 | exit 1 23 | fi 24 | if [[ ! -f "./.github/workflows/release.yml" ]] ; then 25 | echo "/.github/workflows/release.yml does not exist yet" 26 | exit 1 27 | fi 28 | 29 | # Prompt for version number, if not entered 30 | nextVersion="$*" 31 | currentVersion=$(grep "version" "./manifest.json" | cut -d\" -f4) 32 | echo "current version: $currentVersion" 33 | echo -n " next version: " 34 | if [[ -z "$nextVersion" ]]; then 35 | read -r nextVersion 36 | else 37 | echo "$nextVersion" 38 | fi 39 | echo "" 40 | 41 | # Lint 42 | cd "$(dirname "$0")" || exit 1 43 | eslint . --fix --ext=ts # to not lint the main.js files 44 | markdownlint --fix ./README.md 45 | 46 | # set version number in `manifest.json` 47 | sed -E -i '' "s/\"version\".*/\"version\": \"$nextVersion\",/" "manifest.json" 48 | sed -E -i '' "s/\"version\".*/\"version\": \"$nextVersion\",/" "package.json" 49 | 50 | # add version number in `versions.json`, assuming same compatibility 51 | grep -Ev "^$" "versions.json" | grep -v "}" | sed -e '$ d' > temp 52 | minObsidianVersion=$(grep -Ev "^$" "versions.json" | grep -v "}" | tail -n1 | cut -d\" -f4) 53 | # shellcheck disable=SC2129 54 | echo " \"$currentVersion\": \"$minObsidianVersion\"," >> temp 55 | echo " \"$nextVersion\": \"$minObsidianVersion\"" >> temp 56 | echo "}" >> temp 57 | mv temp versions.json 58 | 59 | # update changelog 60 | echo "- $(date +"%Y-%m-%d") release $nextVersion" > ./Changelog.md 61 | git log --pretty=format:"- %ad%x09%s" --date=short | grep -Ev "minor$" | grep -Ev "patch$" | grep -Ev "typos?$" | grep -v "refactoring" | grep -v "Add files via upload" | grep -Ev "\tDelete" | grep -Ev "\tUpdate.*\.md" | sed -E "s/\t\+ /\t/g" >> ./Changelog.md 62 | 63 | # push the manifest and versions JSONs 64 | git add -A 65 | git commit -m "release $nextVersion" 66 | 67 | git pull 68 | git push 69 | 70 | # trigger the release action 71 | git tag "$nextVersion" 72 | git push origin --tags 73 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | PLUGIN_NAME: obsidian-sembr # Change this to match the id of your plugin. 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: "17.x" # node version, get via `node --version` 21 | 22 | - name: Build 23 | id: build # add styles.css in cp line, if it is used 24 | run: | 25 | npm install 26 | npm run build 27 | mkdir ${{ env.PLUGIN_NAME }} 28 | cp main.js manifest.json ${{ env.PLUGIN_NAME }} 29 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 30 | ls 31 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 32 | 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | VERSION: ${{ github.ref }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload-zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload main.js 57 | id: upload-main 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./main.js 64 | asset_name: main.js 65 | asset_content_type: text/javascript 66 | 67 | - name: Upload manifest.json 68 | id: upload-manifest 69 | uses: actions/upload-release-asset@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} 74 | asset_path: ./manifest.json 75 | asset_name: manifest.json 76 | asset_content_type: application/json 77 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "parserOptions": {"sourceType": "module"}, 11 | 12 | "rules": { 13 | // specifically for Obsidian Plugins 14 | "@typescript-eslint/no-unused-vars": "off", 15 | "@typescript-eslint/no-extra-semi": "warn", 16 | "@typescript-eslint/no-empty-function": "warn", 17 | "no-shadow": ["error", { "builtinGlobals": true, "hoist": "all", "allow": ["Editor"] }], 18 | 19 | //----------------------------------- 20 | //----------------------------------- 21 | //----------------------------------- 22 | 23 | // Variable naming 24 | "camelcase": ["error", {"properties": "always", "ignoreImports": true}], 25 | "no-var": "error", 26 | "prefer-const": "warn", 27 | "vars-on-top": "warn", 28 | "sort-vars": "warn", 29 | 30 | // Tabs / Spaces 31 | "no-mixed-spaces-and-tabs": "warn", 32 | "no-empty-function": "warn", 33 | "indent": ["warn", "tab"], 34 | "no-multi-spaces": "warn", 35 | "array-bracket-spacing": "warn", 36 | "space-before-blocks": "warn", 37 | "semi-spacing": "warn", 38 | "object-curly-spacing": ["warn", "always"], 39 | "no-whitespace-before-property": "error", 40 | 41 | // Spacing 42 | "no-empty": "warn", 43 | "arrow-spacing": "warn", 44 | "keyword-spacing": "warn", 45 | "spaced-comment": ["warn", "always", { "exceptions": ["-", "_"] }], 46 | "comma-spacing": "warn", 47 | "comma-style": "warn", 48 | "comma-dangle": ["error", { 49 | "arrays": "never", 50 | "objects": "only-multiline", 51 | }], 52 | "object-curly-newline": ["error", { "multiline": true }], 53 | "curly": ["warn", "multi-or-nest", "consistent"], 54 | "no-multiple-empty-lines": ["warn", { "max": 2 }], 55 | 56 | // Semicolon 57 | "no-extra-semi": "warn", 58 | "semi-style": ["error", "last"], 59 | "semi": ["warn", "always", {"omitLastInOneLineBlock": true }], 60 | 61 | // Strings 62 | "quotes": ["warn", "double"], 63 | "no-useless-concat": "warn", 64 | "no-multi-str": "error", 65 | 66 | // Numbers 67 | "no-magic-numbers": ["error", { "ignore": [-4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "ignoreArrayIndexes": true }], 68 | "no-floating-decimal": "warn", 69 | 70 | // Conditions 71 | "eqeqeq" : "error", 72 | "no-negated-condition": "error", 73 | "no-unneeded-ternary": "error", 74 | "no-nested-ternary": "error", 75 | "yoda": "warn", 76 | "no-mixed-operators": "error", 77 | "no-else-return": ["error", { "allowElseIf": false} ], 78 | "no-lonely-if": "error", 79 | 80 | // Misc 81 | "dot-notation": "error", 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, Editor } from "obsidian"; 2 | /* eslint spaced-comment: "off" */ 3 | 4 | export default class ObsidianSemBr extends Plugin { 5 | 6 | async onload() { 7 | console.log("Semantic Line Breaker Plugin loaded."); 8 | 9 | this.addCommand({ 10 | id: "toggle-sem-br", 11 | name: "Toggle Semantic Line Breaks", 12 | editorCallback: (editor) => this.toggleSemBr(editor), 13 | }); 14 | } 15 | 16 | async onunload() { console.log("Semantic Line Breaker Plugin unloaded.") } 17 | 18 | public removeSemBr (str: string) { 19 | return str.replace (/([.,:;?!—]) ?\n(?!\n)/gm, "$1 "); 20 | } 21 | 22 | async toggleSemBr (editor: Editor) { 23 | // read note 24 | let noteContent = editor.getValue() 25 | .replace(/\n+$/, ""); // remove line breaks at end of file to recognize last line properly 26 | 27 | // cut out yaml header, if the text has it 28 | const yamlHeader = noteContent.match(/^---\n.*?---\n/s); 29 | if (yamlHeader) noteContent = noteContent.replace(yamlHeader[0], ""); 30 | 31 | // cut out code blocks (TODO: use a proper tree for that...) 32 | const noteHasCodeBlock = noteContent.includes("```"); 33 | const codeBlockArr: string[] = []; 34 | let proseTextArr = []; 35 | if (noteHasCodeBlock) { 36 | let i = 0; 37 | noteContent.split("```").forEach(part => { 38 | const evenElementIndex = (i % 2) === 0; 39 | 40 | if (evenElementIndex) proseTextArr.push(part); 41 | else codeBlockArr.push(part); 42 | 43 | i++; 44 | }); 45 | } else { 46 | proseTextArr.push(noteContent); 47 | } 48 | 49 | // TOGGLE SEMBR 50 | const isSemanticLineBreaked = /[.,:;?!—] ?\n(?!\n)/.test(noteContent); 51 | 52 | proseTextArr = proseTextArr.map(prose => { 53 | // Remove sembr 54 | if (isSemanticLineBreaked) return this.removeSemBr(prose); 55 | 56 | // Add sembr 57 | prose = prose.replace ( 58 | /([^|.]{25,}?[^:][.,:;?!—](?: ?\[.+\])?( ))(?!\n\n| |.*\|.*$|p\. [1-9-]+\]|@|\d)(?=[^|.]{25,})/gm, 59 | //(1 )(2 )(3 )(4 )(5) (6 )(7 )(8 ) 60 | //( $1 ) 61 | // 1: 20 chars minimum (non-greedy), all without another . occuring 62 | // pipes (|), too, aren't allowed (tables) 63 | // 2: not ending with :: (dataview inline fields) 64 | // 3: ending with a punctuation as defined in SemBr specification §4-5 65 | // 4: optionally followed by a footnote like `[^1]` 66 | // 5: space after punctuation in prose text 67 | // 6: not followed by two line breaks (blank line), 68 | // another space (Markdown Two-Space Rule), 69 | // or a pipe character somewhere till the end of the line (Table) 70 | // 7: also not followed by pandoc citation syntax, so citations aren't 71 | // split up 72 | // 8: followed by at least 20 chars without punctuation 73 | // (to prevent too short parts afterwards) 74 | 75 | "$1\n"); // add the line break 76 | 77 | // stitch back footnotes 78 | prose = prose.replace( 79 | /\n\[\^.*?(?=\[\n\^|\n\n|$)/gs, 80 | footnote => this.removeSemBr(footnote) 81 | ); 82 | 83 | return prose; 84 | }); 85 | 86 | // put YAML & code blocks back 87 | if (noteHasCodeBlock) { 88 | const tempArr = []; 89 | for (let i = 0; i < proseTextArr.length; i++) { 90 | tempArr.push(proseTextArr[i]); 91 | if (codeBlockArr[i]) tempArr.push(codeBlockArr[i]); 92 | } 93 | noteContent = tempArr.join("```"); 94 | } else { 95 | noteContent = proseTextArr[0]; 96 | } 97 | if (yamlHeader) noteContent = yamlHeader[0] + noteContent; 98 | 99 | // add line break at document end back 100 | noteContent += "\n"; 101 | 102 | // write note 103 | editor.setValue(noteContent); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semantic Line Breaker 2 | 3 | ![](https://img.shields.io/github/downloads/chrisgrieser/obsidian-sembr/total?label=Total%20Downloads&style=plastic) ![](https://img.shields.io/github/v/release/chrisgrieser/obsidian-sembr?label=Latest%20Release&style=plastic) [![](https://img.shields.io/badge/changelog-click%20here-FFE800?style=plastic)](Changelog.md) 4 | 5 | [Obsidian](https://obsidian.md/) Plugin to apply and remove [Semantic Line Breaks](https://sembr.org/). 6 | 7 | ## Table of Contents 8 | 9 | 10 | - [What are Semantic Line Breaks?](#what-are-semantic-line-breaks) 11 | - [What Does this Plugin Do?](#what-does-this-plugin-do) 12 | - [Additions to the official SemBr-Specification](#additions-to-the-official-sembr-specification) 13 | - [Current Limitations](#current-limitations) 14 | - [Installation](#installation) 15 | - [Contribute](#contribute) 16 | - [About the Developer](#about-the-developer) 17 | - [Profiles](#profiles) 18 | - [Donate](#donate) 19 | 20 | 21 | 22 | ## What are Semantic Line Breaks? 23 | At its core, *Semantic Line Breaks* (SemBr) simply means abiding by one rule: 24 | 25 | > When writing text with a compatible markup language, add a line break after each substantial unit of thought. 26 | > — [sembr.org](https://sembr.org/) 27 | 28 | When you have the `Strict Line Breaks` settings enabled in Obsidian, single line breaks are ignored, meaning that in Reading View, the text is displayed normally. 29 | 30 | Using semantic line breaks has three advantages: 31 | 1. __Paragraphs are broken up into units of thought__. This can be helpful for writing and editing text. Think [Atomic Notes](https://zettelkasten.de/posts/create-zettel-from-reading-notes/) but on the level of thoughts. 32 | 2. Most tools in Obsidian work on a per-line-basis, for example `Swap Line Up`. While useful for outlines, those commands are basically useless when writing prose, since your lines are often entire paragraphs consisting of multiple sentences. With semantic line breaks, every line roughly equals a sentence, so that all those __line-based commands now work on a per-sentence-basis__. (This includes most vim commands.) 33 | 3. __Diff Views__, for example using the [Version History Diff Plugin](https://obsidian.md/plugins?id=obsidian-version-history-diff), __become much more useful__, since they now indicate changes in sentences instead of whole paragraphs. [See here for a brief example.](https://github.com/bobheadxi/readable#rationale) 34 | 35 | [You can read more on Semantic Line Breaks here.](https://sembr.org/) 36 | 37 | ## What Does this Plugin Do? 38 | Right now, it simply adds one command, `Toggle Semantic Line Breaks`, which turns your prose into "semantic-line-broken" text. When the note already has semantic line breaks, the command will turn the text back into "normal-line-broken" text. 39 | 40 | ![demo semantic line breaks](/assets/demo-sembr.gif) 41 | 42 | ### Additions to the official SemBr-Specification 43 | - YAML Headers, Dataview inline attributes (`key:: value`), and tables are ignored. 44 | - The [Markdown Two-Space Rule](https://daringfireball.net/projects/markdown/syntax#p) is respected. 45 | - Markdown footnote keys at the end of a sentence are factored in and preserved. 46 | - There is a minimum of 15 characters before a semantic line break is applied, to avoid commas within enumerations getting line-breaked. 47 | 48 | ### Current Limitations 49 | - Indented Code blocks (as opposed to fenced code blocks) are currently not respected by this plugin. 50 | - Prose text with pipe characters (`|`) is ignored, since it's interpreted as table as a precaution. 51 | - Even with the minimum of 15 characters, the plugin will have a few false positives when it comes to enumerations. 52 | 53 | ## Installation 54 | Right now, the plugin is still in beta. It can be installed with the [BRAT Plugin](https://github.com/TfTHacker/obsidian42-brat). 55 | 56 | When published, it will be available in Obsidian's Community Plugin Browser via: `Settings` → `Community Plugins` → `Browse` → Search for *"Semantic Line Breaker"* 57 | 58 | ## Contribute 59 | Please use the [`.eslintrc` configuration located in the repository](.eslintrc) and run eslint before doing a pull request, and please do *not* use `prettier`. 🙂 60 | 61 | ```shell 62 | # Run eslint fixing most common mistakes 63 | eslint --fix *.ts 64 | ``` 65 | 66 | ## About the Developer 67 | In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch! 68 | 69 | 70 | ### Profiles 71 | - [Academic Website](https://chris-grieser.de/) 72 | - [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser) 73 | - [Discord](https://discordapp.com/users/462774483044794368/) 74 | - [GitHub](https://github.com/chrisgrieser/) 75 | - [Twitter](https://twitter.com/pseudo_meta) 76 | - [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/) 77 | 78 | ### Donate 79 | Buy Me a Coffee at ko-fi.com 80 | 81 | If you feel very generous, you may also buy me something from my Amazon wish list. But please donate something to developers who still go to college, before you consider buying me an item from my wish list! 😊 82 | 83 | [Amazon wish list](https://www.amazon.de/hz/wishlist/ls/2C7RIOJPN3K5F?ref_=wl_share) 84 | --------------------------------------------------------------------------------