├── icon.png ├── screenshots ├── logseq_tidy_blocks_v.1.1.0.gif ├── logseq_tidy_blocks_v.1.2.0.gif ├── logseq_tidy_blocks_settings.png └── logseq_tidy_blocks_keyboard_shortcut_demo.gif ├── src ├── index.html └── index.js ├── package.json ├── LICENSE ├── .github └── workflows │ └── publish.yml ├── .gitignore └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyleung/logseq-tidy-blocks-plugin/HEAD/icon.png -------------------------------------------------------------------------------- /screenshots/logseq_tidy_blocks_v.1.1.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyleung/logseq-tidy-blocks-plugin/HEAD/screenshots/logseq_tidy_blocks_v.1.1.0.gif -------------------------------------------------------------------------------- /screenshots/logseq_tidy_blocks_v.1.2.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyleung/logseq-tidy-blocks-plugin/HEAD/screenshots/logseq_tidy_blocks_v.1.2.0.gif -------------------------------------------------------------------------------- /screenshots/logseq_tidy_blocks_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyleung/logseq-tidy-blocks-plugin/HEAD/screenshots/logseq_tidy_blocks_settings.png -------------------------------------------------------------------------------- /screenshots/logseq_tidy_blocks_keyboard_shortcut_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vyleung/logseq-tidy-blocks-plugin/HEAD/screenshots/logseq_tidy_blocks_keyboard_shortcut_demo.gif -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logseq-tidy-blocks-plugin", 3 | "version": "1.2.1", 4 | "description": "Remove whitespace and line breaks and hide block properties to keep blocks nice and tidy", 5 | "main": "dist/index.html", 6 | "targets": { 7 | "main": false 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "parcel build --no-source-maps src/index.html --public-url ./" 12 | }, 13 | "keywords": [], 14 | "author": "vyleung", 15 | "license": "MIT", 16 | "logseq": { 17 | "id": "logseq-tidy-blocks-plugin", 18 | "title": "Tidy Blocks", 19 | "icon": "./icon.png" 20 | }, 21 | "dependencies": { 22 | "@logseq/libs": "^0.0.15", 23 | "driftless": "^2.0.3" 24 | }, 25 | "devDependencies": { 26 | "parcel": "^2.9.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vyleung 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/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: logseq-tidy-blocks-plugin 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: '16.x' # You might need to adjust this value to your own version 22 | - name: Build 23 | id: build 24 | run: | 25 | npm i && npm run build 26 | mkdir ${{ env.PLUGIN_NAME }} 27 | cp README.md package.json icon.png ${{ env.PLUGIN_NAME }} 28 | mv dist ${{ 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 | - name: Create Release 33 | uses: ncipollo/release-action@v1 34 | id: create_release 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | VERSION: ${{ github.ref }} 38 | with: 39 | allowUpdates: true 40 | draft: false 41 | prerelease: false 42 | 43 | - name: Upload zip file 44 | id: upload_zip 45 | uses: actions/upload-release-asset@v1 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | with: 49 | upload_url: ${{ steps.create_release.outputs.upload_url }} 50 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 51 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 52 | asset_content_type: application/zip 53 | 54 | - name: Upload package.json 55 | id: upload_metadata 56 | uses: actions/upload-release-asset@v1 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | with: 60 | upload_url: ${{ steps.create_release.outputs.upload_url }} 61 | asset_path: ./package.json 62 | asset_name: package.json 63 | asset_content_type: application/json 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | .env.production 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | .parcel-cache 81 | 82 | # Next.js build output 83 | .next 84 | out 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | .vscode 114 | 115 | # yarn v2 116 | .yarn/cache 117 | .yarn/unplugged 118 | .yarn/build-state.yml 119 | .yarn/install-state.gz 120 | .pnp.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## logseq-tidy-blocks-plugin 2 | > If you find this plugin – or any of my other [Logseq plugins](https://github.com/vyleung?tab=repositories&q=logseq&type=source) – useful and would like to support me, you can [buy me a coffee](https://www.buymeacoffee.com/vyleung) 🙂 3 | 4 | ## Features 5 | ### Remove empty blocks and whitespace from _one_ block or _multiple_ blocks in [3 ways](#3-ways-to-use-the-plugin) 6 | #### Demo 7 | ![logseq-tidy-blocks-plugin v.1.1.0 demo](screenshots/logseq_tidy_blocks_v.1.1.0.gif) 8 | 9 | ### Toggle the display of block properties in [3 ways](#3-ways-to-use-the-plugin) 10 | - Limitation for manually hiding block properties is that the display state of block properties is NOT remembered when Logseq is reloaded 11 | #### Demo 12 | ![logseq-tidy-blocks-plugin v.1.2.0 demo](screenshots/logseq_tidy_blocks_v.1.2.0.gif) 13 | 14 | ### 3 ways to use the plugin 15 | - Block context menu (right-click on bullet) 16 | - Slash (/) command 17 | - Keyboard shortcuts (can be configured in the [settings](#settings)) 18 | - Usage for one block: Click on the block (as if to edit it) → Activate the keyboard shortcut 19 | - Usage for multiple blocks: Click on the first block (as if to edit it) → Select blocks above (`shift+up`) or below (`shift+down`) → Activate the keyboard 20 | - Demo: ![logseq-tidy-blocks-plugin keyboard shortcut demo](screenshots/logseq_tidy_blocks_keyboard_shortcut_demo.gif) 21 | 22 | ### Settings 23 | - Each time you make changes to the plugin settings, please reload Logseq to ensure that all settings are updated 24 | ![logseq-tidy-blocks-plugin settings](screenshots/logseq_tidy_blocks_settings.png) 25 | 26 | ## Installation 27 | ### Preparation 28 | 1. Click the 3 dots in the righthand corner → `Settings` → `Advanced` → Enable `Developer mode` and `Plug-in system` 29 | 2. Click the 3 dots in the righthand corner → `Plugins` – OR – Use keyboard shortcut `Esc t p` 30 | 31 | ### Load plugin via the marketplace (recommended) 32 | 1. Click the 3 dots in the righthand corner → `Plugins` – OR – Use keyboard shortcut `Esc t p` 33 | 2. Go to the `Marketplace` tab and search for `Tidy Blocks` → Click `Install` 34 | 35 | ### Load plugin manually 36 | 1. Download the [latest release](https://github.com/vyleung/logseq-tidy-blocks-plugin/releases) of the plugin (e.g logseq-tidy-blocks-plugin-v.1.0.0.zip) from Github 37 | 2. Unzip the file 38 | 3. Navigate to plugins (Click the 3 dots → `Plugins` – OR – Use keyboard shortcut `Esc t p`) → `Load unpacked plugin` → Select the folder of the unzipped file 39 | 40 | ## License 41 | MIT 42 | 43 | ## Credits 44 | - Plugin Marketplace Icon: Tidy icons created by Freepik - Flaticon 45 | - Plugin Toolbar Icon: [Material Design Icons](https://materialdesignicons.com/) 46 | - Plugin nspired by the [Obsidian Text Format plugin](https://github.com/Benature/obsidian-text-format) 47 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "@logseq/libs"; 2 | import { setDriftlessTimeout } from "driftless"; 3 | 4 | const settings = [ 5 | { 6 | key: "DeleteEmptyBlocks", 7 | title: "Delete empty blocks?", 8 | description: "Check the box if you would like to delete empty blocks when tidying up", 9 | default: false, 10 | type: "boolean", 11 | enumPicker: "checkbox" 12 | }, 13 | { 14 | key: "KeyboardShortcut_LineBreak", 15 | title: "Keyboard shortcut to tidy block(s) and keep a line break", 16 | description: "This is the the keyboard shortcut to tidy one block or multiple blocks and keep a line break (default: alt+t)", 17 | type: "string", 18 | default: "alt+t" 19 | }, 20 | { 21 | key: "KeyboardShortcut_RemoveAll", 22 | title: "Keyboard shortcut to tidy block(s) and remove all whitespace", 23 | description: "This is the the keyboard shortcut to tidy one block or multiple blocks and remove all whitespace (default: alt+r)", 24 | type: "string", 25 | default: "alt+r" 26 | }, 27 | { 28 | key: "ToggleBlockProperties", 29 | title: "Toggle block properties?", 30 | description: "Automatic: Automatically hide all block properties and have the ability to toggle them | Manual: Use the slash command to hide the properties per block | None: Leave block properties as is (default: manual)", 31 | type: "enum", 32 | enumPicker: "radio", 33 | enumChoices: ["automatic", "manual", "none"], 34 | default: "manual" 35 | }, 36 | { 37 | key: "KeyboardShortcut_ToggleBlockProperties", 38 | title: "Keyboard shortcut to toggle block properties", 39 | description: "This is the the keyboard shortcut to toggle block properties (default: mod+ctrl+x)", 40 | type: "string", 41 | default: "mod+ctrl+x" 42 | } 43 | ] 44 | logseq.useSettingsSchema(settings); 45 | const up_icon = ` 46 | 47 | 48 | `; 49 | const down_icon = ` 50 | 51 | 52 | `; 53 | let tidy_type; 54 | let parent_block_uuid; 55 | let all_blocks_with_properties; 56 | let selected_block_with_properties; 57 | let all_blocks_with_buttons; 58 | let button_block_uuid; 59 | let button_block_properties; 60 | let block_properties; 61 | let toggle_icon_container; 62 | let toggle_icon; 63 | 64 | // ref for traversing through a block and their children: https://gist.github.com/umidjons/6865350#file-walk-dom-js 65 | function tidy(block) { 66 | logseq.Editor.getBlock(block.uuid, {includeChildren: true}).then(tree_block => { 67 | let block_content = tree_block.content; 68 | 69 | // delete empty blocks if indicated in settings 70 | if ((logseq.settings.DeleteEmptyBlocks) && (block_content == "")) { 71 | logseq.Editor.removeBlock(tree_block.uuid); 72 | } 73 | 74 | if (tidy_type == "keep a line break") { 75 | // ref for replacing extra spaces and tabs (1st .replace()): https://stackoverflow.com/questions/5310821/removing-space-and-retaining-the-new-line 76 | // ref for replacing extra line breaks (2nd .replace()): https://stackoverflow.com/questions/22962220/remove-multiple-line-breaks-n-in-javascript 77 | logseq.Editor.updateBlock(tree_block.uuid, block_content.trim().replace(/[ \t]{2,}/gu, " ").replace(/[\r\n]{2,}/g, "\n")); 78 | } 79 | else { 80 | // ref for removing all whitespace: https://github.com/sindresorhus/condense-whitespace 81 | logseq.Editor.updateBlock(tree_block.uuid, block_content.trim().replace(/\s{2,}/gu, " ").replace(/[\r\n]/g, " ")); 82 | } 83 | 84 | if (tree_block.children.length > 0) { 85 | let children_block = tree_block.children; 86 | children_block.forEach(child => { 87 | tidy(child); 88 | }); 89 | } 90 | }); 91 | } 92 | 93 | // tidy one selected block 94 | function tidySelectedBlock(e) { 95 | tidy(e); 96 | logseq.UI.showMsg("Block is tidied!") 97 | } 98 | 99 | // tidy multiple selected blocks 100 | function tidyMultipleSelectedBlocks() { 101 | logseq.Editor.getSelectedBlocks().then(selected_blocks => { 102 | selected_blocks.forEach(selected_block => { 103 | tidy(selected_block); 104 | }); 105 | }); 106 | logseq.UI.showMsg("Blocks are tidied!"); 107 | } 108 | 109 | function tidyBlocks(e) { 110 | // if the uuid exists, one block has been selected; otherwise, multiple blocks have been selected 111 | (e.uuid) ? tidySelectedBlock(e) : tidyMultipleSelectedBlocks(); 112 | } 113 | 114 | logseq.onSettingsChanged(updated_settings => { 115 | if (logseq.settings.ToggleBlockProperties == "automatic") { 116 | const mutation_observer = new MutationObserver((mutations) => { 117 | for (const mutation of mutations) { 118 | insertToggleBlockPropertiesButton("automatic", ""); 119 | } 120 | }); 121 | mutation_observer.observe(parent.document.body, { 122 | childList: true, 123 | subtree: true 124 | }); 125 | logseq.beforeunload(async () => { 126 | mutation_observer.disconnect(); 127 | }); 128 | } 129 | else if (logseq.settings.ToggleBlockProperties == "manual") { 130 | // right click - hide block properties 131 | logseq.Editor.registerBlockContextMenuItem("🧹 Tidy up & hide block properties", async (e) => { 132 | insertToggleBlockPropertiesButton("manual", e); 133 | }); 134 | 135 | // slash command - hide block properties 136 | logseq.Editor.registerSlashCommand("🧹 Tidy up & hide block properties", async (e) => { 137 | insertToggleBlockPropertiesButton("manual", e); 138 | }); 139 | 140 | // keyboard shortcut: hide block properties 141 | logseq.App.registerCommandPalette({ 142 | key: `tidy-blocks-ToggleBlockProperties`, 143 | label: "Tidy up & toggle all block properties", 144 | keybinding: { 145 | binding: logseq.settings.KeyboardShortcut_ToggleBlockProperties, 146 | mode: "global", 147 | } 148 | }, async () => { 149 | logseq.Editor.checkEditing().then(editing_block => { 150 | if (editing_block) { 151 | insertToggleBlockPropertiesButton("manual", editing_block); 152 | } 153 | else { 154 | logseq.Editor.getSelectedBlocks().then(all_selected_blocks => { 155 | all_selected_blocks.forEach(a_selected_block => { 156 | insertToggleBlockPropertiesButton("manual", a_selected_block); 157 | }); 158 | }); 159 | } 160 | }); 161 | }); 162 | } 163 | }); 164 | 165 | function insertToggleBlockPropertiesButton(type, block) { 166 | if (type == "automatic") { 167 | // get all blocks that have properties 168 | all_blocks_with_properties = parent.document.querySelectorAll(".block-properties"); 169 | 170 | if (all_blocks_with_properties) { 171 | // for each block that has properties, if the block-properties div doesn't have an id, add one and insert the toggle button 172 | for (const block_with_properties of all_blocks_with_properties) { 173 | if (!block_with_properties.id) { 174 | parent_block_uuid = block_with_properties.parentElement.id.split("block-content-")[1]; 175 | block_with_properties.id = `tidy-block-properties-${parent_block_uuid}`; 176 | showToggleBlockPropertiesButton(parent_block_uuid); 177 | } 178 | } 179 | } 180 | } 181 | else { 182 | (block.uuid) ? parent_block_uuid = block.uuid : parent_block_uuid = block; 183 | 184 | logseq.Editor.exitEditingMode(); 185 | showToggleBlockPropertiesButton(parent_block_uuid); 186 | 187 | setDriftlessTimeout(() => { 188 | selected_block_with_properties = parent.document.querySelector(`#block-content-${parent_block_uuid} > .block-properties`); 189 | 190 | if (!selected_block_with_properties.id) { 191 | selected_block_with_properties.id = `tidy-block-properties-${parent_block_uuid}`; 192 | } 193 | }, 50); 194 | } 195 | } 196 | 197 | function showToggleBlockPropertiesButton(block_uuid) { 198 | // toggle block properties button 199 | logseq.provideUI({ 200 | key: `tidy-blocks-properties-icon-${block_uuid}`, 201 | path: `div[id^="ls-block"][id$="${block_uuid}"`, 202 | template: 203 | `
204 | ${up_icon} 205 |
` 206 | }); 207 | 208 | logseq.provideStyle(` 209 | .tidy-blocks-properties { 210 | position: absolute; 211 | top: 0; 212 | right: -2.5em; 213 | } 214 | #block-properties-icon-${block_uuid} { 215 | display: flex; 216 | align-items: center; 217 | padding: 0.25em 0.375em; 218 | } 219 | `) 220 | 221 | // hide block properties 222 | setDriftlessTimeout(() => { 223 | parent.document.querySelector(`#block-content-${block_uuid} > .block-properties`).style.display = "none"; 224 | }, 25); 225 | } 226 | 227 | function toggleBlockProperties(e) { 228 | parent_block_uuid = e.id.split("block-properties-container-")[1]; 229 | 230 | block_properties = parent.document.getElementById(`tidy-block-properties-${parent_block_uuid}`); 231 | toggle_icon = parent.document.querySelector(`#block-properties-icon-${parent_block_uuid}`); 232 | 233 | if (block_properties.style.display == "none") { 234 | block_properties.style.display = "block"; 235 | toggle_icon.innerHTML = down_icon; 236 | } 237 | else { 238 | block_properties.style.display = "none"; 239 | toggle_icon.innerHTML = up_icon; 240 | } 241 | } 242 | 243 | function removeToggleBlockPropertiesButton() { 244 | all_blocks_with_buttons = parent.document.querySelectorAll(".tidy-blocks-properties"); 245 | 246 | for (const block_with_buttons of all_blocks_with_buttons) { 247 | if (all_blocks_with_buttons) { 248 | button_block_uuid = block_with_buttons.id.split("block-properties-container-")[1]; 249 | toggle_icon_container = parent.document.querySelector(`div[id*="tidy-blocks-properties-icon-${button_block_uuid}"]`); 250 | button_block_properties = parent.document.querySelector(`#block-content-${button_block_uuid} > .block-properties`); 251 | 252 | logseq.Editor.getBlockProperties(button_block_uuid).then(properties => { 253 | if (Object.keys(properties).length == 0) { 254 | toggle_icon_container.remove(); 255 | } 256 | else { 257 | if (button_block_properties) { 258 | setDriftlessTimeout(() => { 259 | if (button_block_properties.id == "") { 260 | button_block_properties.id = `tidy-block-properties-${button_block_uuid}`; 261 | button_block_properties.style.display = "none"; 262 | } 263 | }, 50); 264 | } 265 | } 266 | }); 267 | } 268 | } 269 | } 270 | 271 | // remove toggle block button if the block has a button, but has no properties 272 | const mutation_observer_buttons = new MutationObserver((changes) => { 273 | for (const change of changes) { 274 | removeToggleBlockPropertiesButton(); 275 | } 276 | }); 277 | mutation_observer_buttons.observe(parent.document.body, { 278 | childList: true, 279 | subtree: true 280 | }); 281 | logseq.beforeunload(async () => { 282 | mutation_observer_buttons.disconnect(); 283 | }); 284 | 285 | const main = async () => { 286 | console.log("logseq-tidy-blocks-plugin loaded"); 287 | 288 | logseq.provideModel ({ 289 | toggle_block_properties(e) { 290 | toggleBlockProperties(e); 291 | } 292 | }); 293 | 294 | // right click - tidy one block (keep a line break) 295 | logseq.Editor.registerBlockContextMenuItem("🧹 Tidy up & keep a line break", async (e) => { 296 | tidy_type = "keep a line break"; 297 | tidySelectedBlock(e); 298 | }); 299 | 300 | // slash command - tidy one block (keep a line break) 301 | logseq.Editor.registerSlashCommand("🧹 Tidy up & keep a line break", async (e) => { 302 | tidy_type = "keep a line break"; 303 | tidySelectedBlock(e); 304 | }); 305 | 306 | // right click - tidy one block (remove all whitespace) 307 | logseq.Editor.registerBlockContextMenuItem("🧹 Tidy up & remove all whitespace", async (e) => { 308 | tidy_type = "remove all whitespace"; 309 | tidySelectedBlock(e); 310 | }); 311 | 312 | // slash command - tidy one block (remove all whitespace) 313 | logseq.Editor.registerSlashCommand("🧹 Tidy up & remove all whitespace", async (e) => { 314 | tidy_type = "remove all whitespace"; 315 | tidySelectedBlock(e); 316 | }); 317 | 318 | // keyboard shortcut: tidy block(s) and keep a line break 319 | logseq.App.registerCommandPalette({ 320 | key: `tidy-blocks-KeyboardShortcut_LineBreak`, 321 | label: "Tidy up & keep a line break", 322 | keybinding: { 323 | binding: logseq.settings.KeyboardShortcut_LineBreak, 324 | mode: "global", 325 | } 326 | }, async (e) => { 327 | tidy_type = "keep a line break"; 328 | tidyBlocks(e); 329 | }); 330 | 331 | // keyboard shortcut: tidy block(s) and remove all linespaces 332 | logseq.App.registerCommandPalette({ 333 | key: `tidy-blocks-KeyboardShortcut_RemoveAll`, 334 | label: "Tidy up & remove all whitespace", 335 | keybinding: { 336 | binding: logseq.settings.KeyboardShortcut_RemoveAll, 337 | mode: "global", 338 | } 339 | }, async (e) => { 340 | tidy_type = "remove all whitespace"; 341 | tidyBlocks(e); 342 | }); 343 | 344 | logseq.provideModel({ 345 | show_settings() { 346 | logseq.showSettingsUI(); 347 | } 348 | }); 349 | 350 | // toolbar icon 351 | logseq.App.registerUIItem("toolbar", { 352 | key: "logseq-tidy-blocks", 353 | template: 354 | ` 355 | 356 | 357 | 358 | ` 359 | }); 360 | } 361 | 362 | logseq.ready(main).catch(console.error); --------------------------------------------------------------------------------