├── .github └── workflows │ └── releases.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── manifest.json ├── package.json ├── rollup.config.js ├── src ├── core.ts ├── main.ts └── setting.ts └── tsconfig.json /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: Build obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | PLUGIN_NAME: url-into-selection 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: "14.x" 20 | - name: Build 21 | id: build 22 | run: | 23 | npm install 24 | npm run build --if-present 25 | mkdir ${{ env.PLUGIN_NAME }} 26 | cp main.js manifest.json ${{ env.PLUGIN_NAME }} 27 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 28 | ls 29 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 30 | - name: Create Release 31 | id: create_release 32 | uses: actions/create-release@v1 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | VERSION: ${{ github.ref }} 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | *.js.map 11 | main.js 12 | 13 | .prettierignore 14 | .prettierrc.json 15 | 16 | # settings 17 | data.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.7.0] 6 | 7 | ### Bug Fixes 8 | * Fixed the plugin so that it works in live preview, closes [#33](https://github.com/denolehov/obsidian-url-into-selection/issues/33) 9 | 10 | ## [1.6.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.1.0...v1.6.0) (2021-04-27) 11 | 12 | 13 | ### Features 14 | 15 | * move cursor to [^](url) ([8dee070](https://github.com/denolehov/obsidian-url-into-selection/commit/8dee070b2c50b40351ba4c6a5cb11d7bae1f25b2)) 16 | * **core.ts:** add support for file path ([e4bd4f6](https://github.com/denolehov/obsidian-url-into-selection/commit/e4bd4f602eda766907ad7bcc30227f7873937c28)), closes [#3](https://github.com/denolehov/obsidian-url-into-selection/issues/3) 17 | * add img embed syntax whitelist ([012a588](https://github.com/denolehov/obsidian-url-into-selection/commit/012a5885fa796bf955ca34e636d7e06da9d5f6cf)), closes [#13](https://github.com/denolehov/obsidian-url-into-selection/issues/13) 18 | * add option to insert ([de57f34](https://github.com/denolehov/obsidian-url-into-selection/commit/de57f348ca6684b0e6da17372af9fa5e6662a6c9)) 19 | * add: insert inline url when nothing is selected ([6df2f5f](https://github.com/denolehov/obsidian-url-into-selection/commit/6df2f5f5dd4e22ee137f4352f3149a198178b326)) 20 | * replace autoselect option with nothingSelected ([37f4259](https://github.com/denolehov/obsidian-url-into-selection/commit/37f4259085f6643f2375fee37d14c3550a66bc95)) 21 | * switch to DOM Clipboard API ([da55fc0](https://github.com/denolehov/obsidian-url-into-selection/commit/da55fc0ac85c16910eba9d81f85766532cd64f1f)) 22 | * update default regex ([bf3fcd8](https://github.com/denolehov/obsidian-url-into-selection/commit/bf3fcd818b31e41c718d56d6d27441c8a8e9881a)) 23 | * update isUrl method to utilize browser URL API ([1254e5b](https://github.com/denolehov/obsidian-url-into-selection/commit/1254e5b513bb65af2d8415219c61ef4c5f6ff6e3)), closes [#3](https://github.com/denolehov/obsidian-url-into-selection/issues/3) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * don't use ![](url) syntax by default if whitelist rules-list is empty ([50df2a5](https://github.com/denolehov/obsidian-url-into-selection/commit/50df2a55cc731f8be592e94a36b6417510d0838f)) 29 | * url with space, angle brackets and parentheses is no longer broken in markdown ([f9f678c](https://github.com/denolehov/obsidian-url-into-selection/commit/f9f678c98654b233c4f6c4d831371fa5c9a4143e)), closes [#16](https://github.com/denolehov/obsidian-url-into-selection/issues/16) 30 | * **isurl:** empty text is no longer recognized as url ([08d8b19](https://github.com/denolehov/obsidian-url-into-selection/commit/08d8b19b8db2a3f2aae5e0cda4640c2d6bfc6a30)) 31 | 32 | ## [1.5.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.1.0...v1.5.0) (2021-04-26) 33 | 34 | 35 | ### Features 36 | 37 | * avoid unintentional auto selection [@AidenLx](https://github.com/AidenLx) ([d81319f](https://github.com/denolehov/obsidian-url-into-selection/commit/d81319f5ee6d8035c29cc4e497f1dc0125e70166)), closes [#12](https://github.com/denolehov/obsidian-url-into-selection/issues/12) 38 | 39 | ## [1.4.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.1.0...v1.4.0) (2021-04-19) 40 | 41 | 42 | ### Features 43 | 44 | * change default regex to support file links ([#3](https://github.com/denolehov/obsidian-url-into-selection/issues/3)) ([8c6b435](https://github.com/denolehov/obsidian-url-into-selection/commit/8c6b435e6eda075ce7d1ff720ba9a1cdb754c2e9)) 45 | 46 | 47 | ## [1.3.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.1.0...v1.3.0) (2021-04-19) 48 | 49 | 50 | ### Features 51 | 52 | * add behavior to native pastes ([9d76f2f](https://github.com/denolehov/obsidian-url-into-selection/commit/9d76f2fb36dcf5bfc228bf6c2102fcb995e9a859)) 53 | * ignore leading whitespace in URL ([#5](https://github.com/denolehov/obsidian-url-into-selection/issues/5)) ([a13a5e4](https://github.com/denolehov/obsidian-url-into-selection/commit/a13a5e4662a9debba920a04034841241a41dcaca)) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * fix issues with referse pasting ([96f1626](https://github.com/denolehov/obsidian-url-into-selection/commit/96f1626de27828f6d5f376561ac4037a77a4f1fe)) 59 | * remove default hotkey to avoid possible conflict ([e414f46](https://github.com/denolehov/obsidian-url-into-selection/commit/e414f463bc78ac1464f745471da9e66f9a652487)) 60 | 61 | ## [1.2.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.0.0...v1.2.0) (2021-03-29) 62 | 63 | 64 | ### Features 65 | 66 | * regular expression for URLs is now exposed in the settings ([6f8f50e](https://github.com/denolehov/obsidian-url-into-selection/commit/6f8f50e55e19758cd90f473678638a0f0c660f1c)), closes [#3](https://github.com/denolehov/obsidian-url-into-selection/issues/3) [#8](https://github.com/denolehov/obsidian-url-into-selection/issues/8) 67 | 68 | ## [1.1.0](https://github.com/denolehov/obsidian-url-into-selection/compare/v1.0.0...v1.1.0) (2020-11-12) 69 | 70 | 71 | ### Features 72 | 73 | * add ability to paste clipboard into URLs and creating clickable links out of it ([c808cc7](https://github.com/denolehov/obsidian-url-into-selection/commit/c808cc73cffd9e2b3fcb80d0eb4895676359e976)), closes [#2](https://github.com/denolehov/obsidian-url-into-selection/issues/2) 74 | * users are no longer required to have text selected for hotkey to work ([f67ce01](https://github.com/denolehov/obsidian-url-into-selection/commit/f67ce019a57aeff3207b802ecee3eca652fb165e)) 75 | 76 | ## 1.0.0 (2020-10-28) 77 | 78 | 79 | ### Features 80 | 81 | * Paste URLs into selection "notion style" ([2c3aabe](https://github.com/denolehov/obsidian-url-into-selection/commit/2c3aabe8b28f08257dfe070b9d23e0bfe1b2b37f)) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paste URL into selection 2 | 3 | Insert links (URLs) into a selected text "notion-style" using regular `Ctrl/Cmd + V` 4 | 5 | Also works the other way around, inserts text into a selected link (URL) by command palette/hotkey(need to be set manually) 6 | 7 | ## Demo 8 | ![example](https://user-images.githubusercontent.com/4748206/98997874-ed55fb80-253d-11eb-9121-709a316a4d1e.gif) 9 | 10 | ## Compatibility 11 | Custom plugins are only available for Obsidian v0.9.8+. 12 | 13 | # Installation 14 | In order to install this plugin, go to "Settings > Third Party Plugins > Paste URL into selection". 15 | 16 | 17 | If you have any kind of feedback or questions, feel free to reach out via GitHub issues or `@evrwhr` on [Obsidian Discord server](https://discord.com/invite/veuWUTm). 18 | 19 | --- 20 | 21 | > If you like what I do, you could consider buying me a coffee. It is unnecessary, but appreciated :) https://www.buymeacoffee.com/evrwhr 22 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "url-into-selection", 3 | "name": "Paste URL into selection", 4 | "description": "Paste URL \"into\" selected text.", 5 | "isDesktopOnly": false, 6 | "js": "main.js", 7 | "version": "1.7.0" 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-url-into-selection", 3 | "version": "1.7.0", 4 | "description": "Paste URL \"into\" selected text", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js", 9 | "release": "standard-version" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@rollup/plugin-commonjs": "^15.1.0", 16 | "@rollup/plugin-node-resolve": "^9.0.0", 17 | "@rollup/plugin-typescript": "^6.0.0", 18 | "assert-never": "^1.2.1", 19 | "commitizen": "^4.2.3", 20 | "cz-conventional-changelog": "^3.3.0", 21 | "file-url": "^4.0.0", 22 | "obsidian": "0.13.11", 23 | "prettier": "2.1.2", 24 | "rollup": "^2.32.1", 25 | "rollup-plugin-node-polyfills": "^0.2.1", 26 | "standard-version": "^9.0.0", 27 | "tslib": "^2.0.3", 28 | "typescript": "^4.0.3", 29 | "url-parse": "^1.5.1" 30 | }, 31 | "config": { 32 | "commitizen": { 33 | "path": "./node_modules/cz-conventional-changelog" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /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 | import nodePolyfills from "rollup-plugin-node-polyfills"; 5 | 6 | export default { 7 | input: "src/main.ts", 8 | output: { 9 | dir: ".", 10 | sourcemap: "inline", 11 | format: "cjs", 12 | exports: "default", 13 | }, 14 | external: ["obsidian"], 15 | plugins: [typescript(), nodePolyfills(), nodeResolve({ browser: true }), commonjs()], 16 | }; 17 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import assertNever from "assert-never"; 2 | import { NothingSelected, PluginSettings } from "setting"; 3 | import fileUrl from "file-url"; 4 | import { Editor, EditorPosition, EditorRange } from "obsidian"; 5 | 6 | // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html 7 | const win32Path = /^[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$/i; 8 | const unixPath = /^(?:\/[^/]+)+\/?$/i; 9 | const testFilePath = (url: string) => win32Path.test(url) || unixPath.test(url); 10 | 11 | /** 12 | * @param editor Obsidian Editor Instance 13 | * @param cbString text on clipboard 14 | * @param settings plugin settings 15 | */ 16 | export default function UrlIntoSelection(editor: Editor, cbString: string, settings: PluginSettings): void; 17 | /** 18 | * @param editor Obsidian Editor Instance 19 | * @param cbEvent clipboard event 20 | * @param settings plugin settings 21 | */ 22 | export default function UrlIntoSelection(editor: Editor, cbEvent: ClipboardEvent, settings: PluginSettings): void; 23 | export default function UrlIntoSelection(editor: Editor, cb: string | ClipboardEvent, settings: PluginSettings): void { 24 | // skip all if nothing should be done 25 | if (!editor.somethingSelected() && settings.nothingSelected === NothingSelected.doNothing) 26 | return; 27 | 28 | if (typeof cb !== "string" && cb.clipboardData === null) { 29 | console.error("empty clipboardData in ClipboardEvent"); 30 | return; 31 | } 32 | 33 | const clipboardText = getCbText(cb); 34 | if (clipboardText === null) return; 35 | 36 | const { selectedText, replaceRange } = getSelnRange(editor, settings); 37 | const replaceText = getReplaceText(clipboardText, selectedText, settings); 38 | if (replaceText === null) return; 39 | 40 | // apply changes 41 | if (typeof cb !== "string") cb.preventDefault(); // prevent default paste behavior 42 | replace(editor, replaceText, replaceRange); 43 | 44 | // if nothing is selected and the nothing selected behavior is to insert [](url) place the cursor between the square brackets 45 | if ((selectedText === "") && settings.nothingSelected === NothingSelected.insertInline) { 46 | editor.setCursor({ ch: replaceRange.from.ch + 1, line: replaceRange.from.line }); 47 | } 48 | } 49 | 50 | function getSelnRange(editor: Editor, settings: PluginSettings) { 51 | let selectedText: string; 52 | let replaceRange: EditorRange | null; 53 | 54 | if (editor.somethingSelected()) { 55 | selectedText = editor.getSelection().trim(); 56 | replaceRange = null; 57 | } else { 58 | switch (settings.nothingSelected) { 59 | case NothingSelected.autoSelect: 60 | replaceRange = getWordBoundaries(editor, settings); 61 | selectedText = editor.getRange(replaceRange.from, replaceRange.to); 62 | break; 63 | case NothingSelected.insertInline: 64 | case NothingSelected.insertBare: 65 | replaceRange = getCursor(editor); 66 | selectedText = ""; 67 | break; 68 | case NothingSelected.doNothing: 69 | throw new Error("should be skipped"); 70 | default: 71 | assertNever(settings.nothingSelected); 72 | } 73 | } 74 | return { selectedText, replaceRange }; 75 | } 76 | 77 | function isUrl(text: string, settings: PluginSettings): boolean { 78 | if (text === "") return false; 79 | try { 80 | // throw TypeError: Invalid URL if not valid 81 | new URL(text); 82 | return true; 83 | } catch (error) { 84 | // settings.regex: fallback test allows url without protocol (http,file...) 85 | return testFilePath(text) || new RegExp(settings.regex).test(text); 86 | } 87 | } 88 | 89 | function isImgEmbed(text: string, settings: PluginSettings): boolean { 90 | const rules = settings.listForImgEmbed 91 | .split("\n") 92 | .filter((v) => v.length > 0) 93 | .map((v) => new RegExp(v)); 94 | for (const reg of rules) { 95 | if (reg.test(text)) return true; 96 | } 97 | return false; 98 | } 99 | 100 | /** 101 | * Validate that either the text on the clipboard or the selected text is a link, and if so return the link as 102 | * a markdown link with the selected text as the link's text, or, if the value on the clipboard is not a link 103 | * but the selected text is, the value of the clipboard as the link's text. 104 | * If the link matches one of the image url regular expressions return a markdown image link. 105 | * @param clipboardText text on the clipboard. 106 | * @param selectedText highlighted text 107 | * @param settings plugin settings 108 | * @returns a mardown link or image link if the clipboard or selction value is a valid link, else null. 109 | */ 110 | function getReplaceText(clipboardText: string, selectedText: string, settings: PluginSettings): string | null { 111 | 112 | let linktext: string; 113 | let url: string; 114 | 115 | if (isUrl(clipboardText, settings)) { 116 | linktext = selectedText; 117 | url = clipboardText; 118 | } else if (isUrl(selectedText, settings)) { 119 | linktext = clipboardText; 120 | url = selectedText; 121 | } else return null; // if neither of two is an URL, the following code would be skipped. 122 | 123 | const imgEmbedMark = isImgEmbed(clipboardText, settings) ? "!" : ""; 124 | 125 | url = processUrl(url); 126 | 127 | if (selectedText === "" && settings.nothingSelected === NothingSelected.insertBare) { 128 | return `<${url}>`; 129 | } else { 130 | return imgEmbedMark + `[${linktext}](${url})`; 131 | } 132 | } 133 | 134 | /** Process file url, special characters, etc */ 135 | function processUrl(src: string): string { 136 | let output; 137 | if (testFilePath(src)) { 138 | output = fileUrl(src, { resolve: false }); 139 | } else { 140 | output = src; 141 | } 142 | 143 | if (/[<>]/.test(output)) 144 | output = output.replace("<", "%3C").replace(">", "%3E"); 145 | 146 | return /[\(\) ]/.test(output) ? `<${output}>` : output; 147 | } 148 | 149 | function getCbText(cb: string | ClipboardEvent): string | null { 150 | let clipboardText: string; 151 | 152 | if (typeof cb === "string") { 153 | clipboardText = cb; 154 | } else { 155 | if (cb.clipboardData === null) { 156 | console.error("empty clipboardData in ClipboardEvent"); 157 | return null; 158 | } else { 159 | clipboardText = cb.clipboardData.getData("text"); 160 | } 161 | } 162 | return clipboardText.trim(); 163 | } 164 | 165 | function getWordBoundaries(editor: Editor, settings: PluginSettings): EditorRange { 166 | const cursor = editor.getCursor(); 167 | const line = editor.getLine(cursor.line); 168 | let wordBoundaries = findWordAt(line, cursor);; 169 | 170 | // If the token the cursor is on is a url, grab the whole thing instead of just parsing it like a word 171 | let start = wordBoundaries.from.ch; 172 | let end = wordBoundaries.to.ch; 173 | while (start > 0 && !/\s/.test(line.charAt(start - 1))) --start; 174 | while (end < line.length && !/\s/.test(line.charAt(end))) ++end; 175 | if (isUrl(line.slice(start, end), settings)) { 176 | wordBoundaries.from.ch = start; 177 | wordBoundaries.to.ch = end; 178 | } 179 | return wordBoundaries; 180 | } 181 | 182 | const findWordAt = (() => { 183 | const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; 184 | 185 | function isWordChar(char: string) { 186 | return /\w/.test(char) || char > "\x80" && 187 | (char.toUpperCase() != char.toLowerCase() || nonASCIISingleCaseWordChar.test(char)); 188 | } 189 | 190 | return (line: string, pos: EditorPosition): EditorRange => { 191 | let check; 192 | let start = pos.ch; 193 | let end = pos.ch; 194 | (end === line.length) ? --start : ++end; 195 | const startChar = line.charAt(pos.ch); 196 | if (isWordChar(startChar)) { 197 | check = (ch: string) => isWordChar(ch); 198 | } else if (/\s/.test(startChar)) { 199 | check = (ch: string) => /\s/.test(ch); 200 | } else { 201 | check = (ch: string) => (!/\s/.test(ch) && !isWordChar(ch)); 202 | } 203 | 204 | while (start > 0 && check(line.charAt(start - 1))) --start; 205 | while (end < line.length && check(line.charAt(end))) ++end; 206 | return { from: { line: pos.line, ch: start }, to: { line: pos.line, ch: end } }; 207 | }; 208 | })(); 209 | 210 | function getCursor(editor: Editor): EditorRange { 211 | return { from: editor.getCursor(), to: editor.getCursor() }; 212 | } 213 | 214 | function replace(editor: Editor, replaceText: string, replaceRange: EditorRange | null = null): void { 215 | // replaceRange is only not null when there isn't anything selected. 216 | if (replaceRange && replaceRange.from && replaceRange.to) { 217 | editor.replaceRange(replaceText, replaceRange.from, replaceRange.to); 218 | } 219 | // if word is null or undefined 220 | else editor.replaceSelection(replaceText); 221 | } 222 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, MarkdownView, Plugin } from "obsidian"; 2 | import UrlIntoSelection from "./core"; 3 | import { 4 | PluginSettings, 5 | UrlIntoSelectionSettingsTab, 6 | DEFAULT_SETTINGS, 7 | } from "setting"; 8 | 9 | export default class UrlIntoSel_Plugin extends Plugin { 10 | settings: PluginSettings; 11 | 12 | // pasteHandler = (cm: CodeMirror.Editor, e: ClipboardEvent) => UrlIntoSelection(cm, e, this.settings); 13 | pasteHandler = (evt: ClipboardEvent, editor: Editor) => UrlIntoSelection(editor, evt, this.settings); 14 | 15 | 16 | async onload() { 17 | console.log("loading url-into-selection"); 18 | await this.loadSettings(); 19 | 20 | this.addSettingTab(new UrlIntoSelectionSettingsTab(this.app, this)); 21 | this.addCommand({ 22 | id: "paste-url-into-selection", 23 | name: "", 24 | editorCallback: async (editor: Editor) => { 25 | const clipboardText = await navigator.clipboard.readText(); 26 | UrlIntoSelection(editor, clipboardText, this.settings); 27 | }, 28 | }); 29 | 30 | this.app.workspace.on("editor-paste", this.pasteHandler); 31 | } 32 | 33 | onunload() { 34 | console.log("unloading url-into-selection"); 35 | 36 | this.app.workspace.off("editor-paste", this.pasteHandler); 37 | } 38 | 39 | async loadSettings() { 40 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 41 | } 42 | 43 | async saveSettings() { 44 | await this.saveData(this.settings); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/setting.ts: -------------------------------------------------------------------------------- 1 | import UrlIntoSel_Plugin from "main"; 2 | import { PluginSettingTab, Setting } from "obsidian"; 3 | 4 | export const enum NothingSelected { 5 | /** Default paste behaviour */ 6 | doNothing, 7 | /** Automatically select word surrounding the cursor */ 8 | autoSelect, 9 | /** Insert `[](url)` */ 10 | insertInline, 11 | /** Insert `` */ 12 | insertBare, 13 | } 14 | 15 | export interface PluginSettings { 16 | regex: string; 17 | nothingSelected: NothingSelected; 18 | listForImgEmbed: string; 19 | } 20 | 21 | export const DEFAULT_SETTINGS: PluginSettings = { 22 | regex: /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ 23 | .source, 24 | nothingSelected: NothingSelected.doNothing, 25 | listForImgEmbed: "", 26 | }; 27 | 28 | export class UrlIntoSelectionSettingsTab extends PluginSettingTab { 29 | display() { 30 | let { containerEl } = this; 31 | const plugin: UrlIntoSel_Plugin = (this as any).plugin; 32 | 33 | containerEl.empty(); 34 | containerEl.createEl("h2", { text: "URL-into-selection Settings" }); 35 | 36 | new Setting(containerEl) 37 | .setName("Fallback Regular expression") 38 | .setDesc( 39 | "Regular expression used to match URLs when default match fails." 40 | ) 41 | .addText((text) => 42 | text 43 | .setPlaceholder("Enter regular expression here..") 44 | .setValue(plugin.settings.regex) 45 | .onChange(async (value) => { 46 | if (value.length > 0) { 47 | plugin.settings.regex = value; 48 | await plugin.saveSettings(); 49 | } 50 | }) 51 | ); 52 | new Setting(containerEl) 53 | .setName("Behavior on pasting URL when nothing is selected") 54 | .setDesc("Auto Select: Automatically select word surrounding the cursor.") 55 | .addDropdown((dropdown) => { 56 | const options: Record = { 57 | 0: "Do nothing", 58 | 1: "Auto Select", 59 | 2: "Insert [](url)", 60 | 3: "Insert ", 61 | }; 62 | 63 | dropdown 64 | .addOptions(options) 65 | .setValue(plugin.settings.nothingSelected.toString()) 66 | .onChange(async (value) => { 67 | plugin.settings.nothingSelected = +value; 68 | await plugin.saveSettings(); 69 | this.display(); 70 | }); 71 | }); 72 | new Setting(containerEl) 73 | .setName("Whitelist for image embed syntax") 74 | .setDesc( 75 | createFragment((el) => { 76 | el.appendText( 77 | "![selection](url) will be used for URL that matches the following list." 78 | ); 79 | el.createEl("br"); 80 | el.appendText("Rules are regex-based, split by line break."); 81 | }) 82 | ) 83 | .addTextArea((text) => { 84 | text 85 | .setPlaceholder("Example:\nyoutu.?be|vimeo") 86 | .setValue(plugin.settings.listForImgEmbed) 87 | .onChange((value) => { 88 | plugin.settings.listForImgEmbed = value; 89 | plugin.saveData(plugin.settings); 90 | return text; 91 | }); 92 | text.inputEl.rows = 6; 93 | text.inputEl.cols = 25; 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "outDir": "dist", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "module": "ESNext", 8 | "target": "es5", 9 | "allowJs": true, 10 | "noImplicitAny": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "lib": ["dom", "es5", "scripthost", "es2015"] 14 | }, 15 | "include": ["src/**/*.ts"], 16 | "exclude": ["dist"] 17 | } 18 | --------------------------------------------------------------------------------