@docs.plus/extension-hyperlink 1.5.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2026 Hossein Marzban
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.
package/README.md CHANGED
@@ -1,454 +1,204 @@
1
- # Hyperlink
1
+ # @docs.plus/extension-hyperlink
2
2
 
3
+ <a href="https://docs.plus"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/docs-plus/docs.plus/main/apps/webapp/public/badges/badge-docsplus-dark.svg"><img alt="docs.plus" height="20" src="https://raw.githubusercontent.com/docs-plus/docs.plus/main/apps/webapp/public/badges/badge-docsplus.svg"></picture></a>
3
4
  [![Version](https://img.shields.io/npm/v/@docs.plus/extension-hyperlink.svg?label=version)](https://www.npmjs.com/package/@docs.plus/extension-hyperlink)
4
5
  [![Downloads](https://img.shields.io/npm/dm/@docs.plus/extension-hyperlink.svg)](https://npmcharts.com/compare/@docs.plus/extension-hyperlink)
5
6
  [![License](https://img.shields.io/npm/l/@docs.plus/extension-hyperlink.svg)](https://www.npmjs.com/package/@docs.plus/extension-hyperlink)
7
+ [![Discord](https://img.shields.io/badge/discord-community-5865F2?logo=discord&logoColor=white)](https://discord.gg/2EmAjmgZ8)
6
8
 
7
- The Link extension adds support for `<a>` tags to the editor. The extension is headless, there is no actual <u>**UI**</u> to add, <u>modify</u> or <u>delete</u> links. The usage example below uses the native JavaScript prompt to show you how that could work.
9
+ <p align="center">
10
+ <picture>
11
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/demo-dark.gif">
12
+ <img alt="Typing "docs.plus" autolinks it, clicking the link opens a preview with copy, edit and remove, and Mod-K on a selected word opens the create-link form" width="640" src="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/demo-light.gif">
13
+ </picture>
14
+ </p>
8
15
 
9
- In a real world application, you would probably add a more sophisticated user interface.
16
+ Tiptap hyperlink mark with optional prebuilt popovers for creating, previewing, and editing links.
10
17
 
11
- ## Installation
18
+ It replaces the `@tiptap/extension-link` mark that StarterKit v3 bundles.
12
19
 
13
- ````sh
14
- npm install @docs.plus/extension-hyperlink
15
- ````
20
+ [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/docs-plus/docs.plus/tree/main/extensions/extension-hyperlink/examples/vanilla?file=main.ts)
16
21
 
17
- ## Settings
22
+ ## Why use it
18
23
 
19
- ### protocols
24
+ - **Autolink covers more than web URLs.** As you type, bare domains get `https://`, emails become `mailto:`, and E.164 phones become `tel:`. App schemes from a catalog of 47, such as `whatsapp://`, autolink too.
25
+ - **The link UI is included.** Prebuilt create, preview, and edit popovers ship with the package. `Mod-k` opens the create-link form with no configuration. `@tiptap/extension-link` binds no keyboard shortcut.
26
+ - **Every boundary checks the scheme.** `isSafeHref` blocks `javascript:`, `data:`, `vbscript:`, `file:`, and `blob:` on parse, import, write, serialize, and navigation. An embedded tab or newline cannot hide a scheme from the gate.
27
+ - **Your `@tiptap/extension-link` configuration ports over.** `isAllowedUri` takes the same `(uri, ctx)` shape as `@tiptap/extension-link`, so an existing policy works unchanged. `setLink`, `unsetLink`, and `toggleLink` stay as aliases. `[label](url)` round-trips with `@tiptap/markdown`.
20
28
 
21
- Additional custom protocols you would like to be recognized as links.
29
+ ## Install
22
30
 
23
- Default: `[]`
31
+ ```sh
32
+ npm install @docs.plus/extension-hyperlink
33
+ ```
24
34
 
25
- ````js
26
- Hyperlink.configure({
27
- protocols: ['ftp', 'mailto'],
28
- })
29
- ````
35
+ Or use `pnpm add @docs.plus/extension-hyperlink`, `yarn add @docs.plus/extension-hyperlink`, or `bun add @docs.plus/extension-hyperlink`.
30
36
 
31
- By default, [linkify](https://linkify.js.org/docs/) adds `//` to the end of a protocol however this behavior can be changed by passing `optionalSlashes` option
37
+ Requires **`@tiptap/core` ^3.31.3** and **`@tiptap/pm` ^3.31.3** (Tiptap 3.x).
32
38
 
33
- ````js
34
- Hyperlink.configure({
35
- protocols: [
36
- {
37
- scheme: 'tel',
38
- optionalSlashes: true
39
- }
40
- ]
41
- })
42
- ````
39
+ This package imports no React, Vue, or Next.js code. Use it from a plain page, Vite, React, Vue, Svelte, Next.js, Nuxt, or SvelteKit. Create the editor in the browser.
43
40
 
44
- ### autolink
41
+ React Native has no DOM. Load the editor in a web view.
45
42
 
46
- If enabled, it adds links as you type.
43
+ Installs two runtime dependencies, `@floating-ui/dom` and `linkifyjs`. The popover engine and the tooltip ship inside `dist`, so they add no third package.
47
44
 
48
- Default: `true`
45
+ Upgrading from `1.x`? Option names, command names, and CSS class names all changed — see [Migrating from 1.x](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/migration.md#migrating-from-1x).
49
46
 
50
- ````js
51
- Hyperlink.configure({
52
- autolink: false,
53
- })
54
- ````
47
+ ## Quickstart
55
48
 
56
- ### openOnClick
49
+ [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/docs-plus/docs.plus/tree/main/extensions/extension-hyperlink/examples/vanilla?file=main.ts)
57
50
 
58
- If enabled, links will be opened on click.
51
+ Run this Quickstart in your browser first, with nothing to install. The app lives in [`examples/vanilla`](https://github.com/docs-plus/docs.plus/tree/main/extensions/extension-hyperlink/examples/vanilla).
59
52
 
60
- Default: `true`
53
+ The host page needs one mount point: `<div id="editor"></div>`. The snippet also imports `@tiptap/starter-kit`. Add it with `npm install @tiptap/starter-kit` when your app has none yet.
61
54
 
62
- ````js
63
- Hyperlink.configure({
64
- openOnClick: false,
55
+ ```ts
56
+ import { Editor } from '@tiptap/core'
57
+ import StarterKit from '@tiptap/starter-kit'
58
+ import {
59
+ Hyperlink,
60
+ createHyperlinkPopover,
61
+ editHyperlinkPopover,
62
+ previewHyperlinkPopover
63
+ } from '@docs.plus/extension-hyperlink'
64
+ import '@docs.plus/extension-hyperlink/styles.css'
65
+
66
+ const editor = new Editor({
67
+ element: document.querySelector('#editor'),
68
+ content: '<p>Try <a href="https://example.com">this link</a>.</p>',
69
+ extensions: [
70
+ // Disable StarterKit's bundled link mark — see Caveats.
71
+ StarterKit.configure({ link: false }),
72
+ Hyperlink.configure({
73
+ popovers: {
74
+ previewHyperlink: previewHyperlinkPopover,
75
+ editHyperlink: editHyperlinkPopover,
76
+ createHyperlink: createHyperlinkPopover
77
+ }
78
+ })
79
+ ]
65
80
  })
66
- ````
67
-
68
- ### linkOnPaste
69
-
70
- Adds a link to the current selection if the pasted content only contains an url.
81
+ ```
71
82
 
72
- Default: `true`
83
+ The `styles.css` import is optional. The snippet also styles no document link — see [Popovers](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/popovers.md) and [Style document links](#style-document-links).
73
84
 
74
- ````js
75
- Hyperlink.configure({
76
- linkOnPaste: false,
77
- })
78
- ````
85
+ You should see a paragraph with one link. Click the link to open the preview popover, with copy, edit, and remove actions. Type `example.org` and a space, and the text becomes a link. Select a word and press `Mod-k` (Cmd on macOS, Ctrl elsewhere) to open the create-link form.
79
86
 
80
- ### HTMLAttributes
87
+ ## Caveats
81
88
 
82
- Custom HTML attributes that should be added to the rendered HTML tag.
89
+ - **`StarterKit.configure({ link: false })` is required, not stylistic.** StarterKit v3 bundles `@tiptap/extension-link` by default. The two marks collide on the `setLink` / `unsetLink` / `toggleLink` command names and on the `a[href]` parse rule. Your `extensions` array order then decides each contest, with no warning. If the upstream mark wins, this package's `isSafeHref` gate never runs on parsed or pasted HTML, and its popovers never attach.
90
+ - **A click on a link needs `popovers.previewHyperlink`.** The three openers fall back to the prebuilt factory when a slot is `null`. The click handler does not: leave `previewHyperlink` at `null` and a click on a link opens no popover.
91
+ - **`linkOnPaste: false` does not stop every paste from linking.** The linkify paste rule stays registered, so a pasted bare URL still becomes a link. To stop that path too, set `shouldAutoLink: () => false`, which every autolink path consults.
92
+ - **`HTMLAttributes.target` reaches the DOM.** `HTMLAttributes: { target: '_blank' }` does emit `<a target="_blank">`. A browser-driven `target="_blank"` navigation skips the click gate. Leave `target` at `null` and let the click handler open the link.
83
93
 
84
- ````js
85
- Hyperlink.configure({
86
- HTMLAttributes: {
87
- class: 'my-custom-class',
88
- },
89
- })
90
- ````
94
+ The full list, with the reason for each, is in [Caveats](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/api.md#caveats).
91
95
 
92
- ### Modals
96
+ ## Common tasks
93
97
 
94
- The Modals configuration option lets you incorporate an interactive user interface similar to Google Docs for **setting** and **previewing** hyperlinks. This provides users with a more intuitive and interactive experience;
98
+ ### Set a link from your own button
95
99
 
96
- - [Dive into the code](https://github.com/HMarzban/extension-hyperlink/blob/4f37ffa18237f10d76c316844b1c2ab20b751fe9/packages/nextjs/src/components/Tiptap.tsx#L21-L28)
97
- - [Demo](https://github.com/HMarzban/extension-hyperlink#test-drive-with-our-demo-)
98
-
99
- <details>
100
- <summary>The `previewHyperlinkModal` function</summary>
100
+ Every command lands on `editor.commands` and on `editor.chain()`. On `editor.commands`, each returns `boolean`, and `false` marks a no-op.
101
101
 
102
102
  ```ts
103
- function previewHyperlinkModal(options) {
104
- const href = options.link.href;
105
-
106
- const hyperlinkModal = document.createElement("div");
107
- const removeButton = document.createElement("button");
108
- const copyButton = document.createElement("button");
109
-
110
- const newBubble = document.createElement("div");
111
- newBubble.classList.add("hyperlink-preview-modal__metadata");
103
+ editor.chain().focus().setHyperlink({ href: 'https://example.com' }).run()
104
+ editor.getAttributes('hyperlink').href // read the current href
105
+ ```
112
106
 
113
- const hrefTitle = document.createElement("a");
114
- hrefTitle.setAttribute("target", "_blank");
115
- hrefTitle.setAttribute("rel", "noreferrer");
116
- hrefTitle.setAttribute("href", href);
117
- hrefTitle.innerText = href;
107
+ `setHyperlink` returns `false` when `href` is empty, when the [composed gate](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/api.md#validate-vs-isalloweduri) rejects it, or when the schema cannot apply the mark there. See [Commands](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/api.md#commands) for the full list.
118
108
 
119
- newBubble.append(hrefTitle);
109
+ ### Open the create-link form
120
110
 
121
- hyperlinkModal.classList.add("hyperlink-preview-modal");
111
+ `Mod-k` calls this command. Call it from your own toolbar button too:
122
112
 
123
- removeButton.classList.add("hyperlink-preview-modal__remove-button");
124
- removeButton.innerHTML = "remove";
113
+ ```ts
114
+ editor.commands.openCreateHyperlinkPopover()
115
+ ```
125
116
 
126
- copyButton.classList.add("hyperlink-preview-modal__copy-button");
127
- copyButton.innerHTML = "copy";
117
+ `openCreateHyperlinkPopover` opens UI, so `editor.can().openCreateHyperlinkPopover()` reports availability without mounting anything. With nothing selected, the prebuilt form inserts the typed URL as its own link text.
128
118
 
129
- removeButton.addEventListener("click", () => {
130
- options.tippy.hide();
131
- return options.editor.chain().focus().unsetHyperlink().run();
132
- });
119
+ ### Style document links
133
120
 
134
- copyButton.addEventListener("click", () => {
135
- options.tippy.hide();
136
- navigator.clipboard.writeText(href);
137
- });
121
+ The `styles.css` stylesheet skins the popovers only. The mark renders a plain `<a>` with no class, so style your document links yourself:
138
122
 
139
- hyperlinkModal.append(newBubble, copyButton, removeButton);
123
+ ```ts
124
+ Hyperlink.configure({ HTMLAttributes: { class: 'my-link' } })
125
+ ```
140
126
 
141
- return hyperlinkModal;
127
+ ```css
128
+ a.my-link {
129
+ color: #2563eb;
130
+ text-decoration: underline;
142
131
  }
143
-
144
132
  ```
145
133
 
146
- </details>
147
-
148
- <details>
149
- <summary>The `setHyperlinkModal` function</summary>
150
-
151
- ```ts
152
- import { Editor } from "@tiptap/core";
153
- import { find } from "linkifyjs";
154
-
155
- let tooltip = undefined;
156
-
157
- function setHyperlinkModal(options) {
158
- // Create the tooltip instance
159
- if (!tooltip) tooltip = new options.Tooltip(options);
160
-
161
- // Initialize the tooltip
162
- let { tippyModal } = tooltip.init();
163
-
164
- const hyperlinkModal = document.createElement("div");
165
- const buttonsWrapper = document.createElement("div");
166
- const inputsWrapper = document.createElement("div");
167
-
168
- hyperlinkModal.classList.add("hyperlink-set-modal");
169
-
170
- buttonsWrapper.classList.add("hyperlink-set-modal__buttons-wrapper");
171
- inputsWrapper.classList.add("hyperlink-set-modal__inputs-wrapper");
172
-
173
- // create a form that contain url input and a button for submit
174
- const form = document.createElement("form");
175
- const input = document.createElement("input");
176
- const button = document.createElement("button");
177
-
178
- input.setAttribute("type", "text");
179
- input.setAttribute("placeholder", "https://example.com");
180
- button.setAttribute("type", "submit");
181
- button.innerText = "Submit";
182
-
183
- inputsWrapper.append(input);
184
- buttonsWrapper.append(button);
185
- form.append(inputsWrapper, buttonsWrapper);
186
-
187
- hyperlinkModal.append(form);
188
-
189
- tippyModal.innerHTML = "";
190
- tippyModal.append(hyperlinkModal);
191
- tooltip.update(options.editor.view, {
192
- arrow: options.roundArrow,
193
- });
194
-
195
- // make sure
196
- setTimeout(() => input.focus();, 100);
197
-
198
- // event listenr for submit button
199
- form.addEventListener("submit", (event) => {
200
- event.preventDefault();
201
- const url = input.value;
134
+ For theme tokens, dark mode, and class names, see [Styling](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/styling.md).
202
135
 
203
- if (!url) return;
136
+ ## Gallery
204
137
 
205
- const sanitizeURL = find(url)
206
- .filter((link) => link.isLink)
207
- .filter((link) => {
208
- if (options.validate) {
209
- return options.validate(link.value);
210
- }
211
- return true;
212
- })
213
- .at(0);
138
+ Prebuilt create, preview, and edit popovers (`styles.css` + `popovers` config). Each screenshot has a light and a dark version, and follows your system preference.
214
139
 
215
- if (!sanitizeURL?.href) return;
216
-
217
- tooltip?.hide();
140
+ <details>
141
+ <summary><strong>Create</strong> — Mod+K</summary>
218
142
 
219
- return options.editor
220
- .chain()
221
- .setMark(options.extentionName, { href: sanitizeURL.href })
222
- .setMeta("preventautohyperlink", true)
223
- .run();
224
- });
225
- }
226
- ```
143
+ <p align="center">
144
+ <picture>
145
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/create-dark.png">
146
+ <img alt="Create-link popover with URL field and Apply button" width="640" src="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/create-light.png">
147
+ </picture>
148
+ </p>
227
149
 
228
150
  </details>
229
151
 
230
152
  <details>
231
- <summary>The Hyperlink styles.scss</summary>
232
-
233
- ```scss
234
- .tippy-box {
235
- .hyperlink-preview-modal,
236
- .hyperlink-set-modal,
237
- .hyperlink-edit-modal {
238
- background-color: #fff;
239
- border-radius: 10px;
240
- border: 1px solid #dadce0;
241
- display: flex;
242
- align-items: center;
243
- justify-content: space-between;
244
- padding: 6px 6px;
245
- box-shadow: 0 1px 3px 1px rgba(60, 64, 67, 0.15);
246
- margin-top: -6px;
247
-
248
- &__metadata {
249
- width: 200px;
250
- display: flex;
251
- align-items: center;
252
- justify-content: flex-end;
253
- flex-direction: row-reverse;
254
- a {
255
- font-size: 0.9rem;
256
- margin-right: 6px;
257
- white-space: nowrap;
258
- overflow: hidden;
259
- text-overflow: ellipsis;
260
- }
261
- img {
262
- width: 18px;
263
- height: 18px;
264
- border-radius: 50%;
265
- margin-right: 8px;
266
- }
267
- }
268
-
269
- &__remove-button,
270
- &__edit-button,
271
- &__copy-button,
272
- &__apply-button {
273
- width: 30px;
274
- height: 30px;
275
- border-radius: 50%;
276
- margin: 0 0.25rem;
277
- display: flex;
278
- align-items: center;
279
- justify-content: center;
280
- transition: background-color 0.1s ease-in-out;
281
- &:hover {
282
- background-color: #eee;
283
- }
284
- > svg {
285
- width: 16px;
286
- height: 16px;
287
- }
288
- }
289
-
290
- form {
291
- display: flex;
292
- align-items: flex-end;
293
- width: 100%;
294
- input {
295
- border: 1px solid #dadce0;
296
- border-radius: 6px;
297
- padding: 0.4rem 0.8rem;
298
- margin-bottom: 0.2rem;
299
- width: 100%;
300
- &:last-of-type {
301
- margin-bottom: 0;
302
- }
303
- }
304
- .hyperlink-set-modal__buttons-wrapper,
305
- .hyperlink-edit-modal__buttons-wrapper {
306
- margin-left: 8px;
307
- button {
308
- border-radius: 6px;
309
- padding: 4px 14px;
310
- width: 70px;
311
- margin-bottom: 0.2rem;
312
- color: #1a73e8;
313
- &:hover {
314
- background: rgba(26, 115, 232, 0.04);
315
- color: #174ea6;
316
- }
317
- }
318
- }
319
- }
320
- }
153
+ <summary><strong>Preview</strong> — click a link</summary>
321
154
 
322
- .tippy-svg-arrow {
323
- top: -6px !important;
324
- }
325
- }
326
- ```
155
+ <p align="center">
156
+ <picture>
157
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/preview-dark.png">
158
+ <img alt="Preview popover on a link — copy, edit, and remove actions" width="640" src="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/preview-light.png">
159
+ </picture>
160
+ </p>
327
161
 
328
162
  </details>
329
163
 
330
- ````ts
331
- import {
332
- Hyperlink,
333
- previewHyperlinkModal,
334
- setHyperlinkModal
335
- } from "@docs.plus/extension-hyperlink";
336
-
337
- Hyperlink.configure({
338
- modals: {
339
- previewHyperlink: (data) => {
340
- return previewHyperlinkModal(data);
341
- },
342
- setHyperlink: (data) => {
343
- return setHyperlinkModal(data);
344
- },
345
- },
346
- })
347
- ````
348
-
349
- > Note: The `previewHyperlinkModal` and `setHyperlinkModal` modals are prebuilt modal boxes. You can use them or create your own modal boxes. To learn how to create your own modal boxes, please check the [source code](https://github.com/HMarzban/extension-hyperlink/tree/main/packages/extension-hyperlink/src/modals)
350
-
351
- <Su>
352
-
353
- ### Removing and overriding existing html attributes
354
-
355
- You can add `rel: null` to HTMLAttributes to remove the default `rel="noopener noreferrer nofollow"`. You can also override the default by using `rel: "your-value"`.
356
-
357
- This can also be used to change the `target` from the default value of `_blank`.
358
-
359
- ````js
360
- Hyperlink.configure({
361
- HTMLAttributes: {
362
- // Change rel to different value
363
- // Allow search engines to follow links(remove nofollow)
364
- rel: 'noopener noreferrer',
365
- // Remove target entirely so links open in current tab
366
- target: null,
367
- },
368
- })
369
- ````
370
-
371
- ### validate
372
-
373
- A function that validates every autolinked link. If it exists, it will be called with the link href as argument. If it returns `false`, the link will be removed.
374
-
375
- Can be used to set rules for example excluding or including certain domains, tlds, etc.
376
-
377
- ````js
378
- // only autolink urls with a protocol
379
- Hyperlink.configure({
380
- validate: href => /^https?:\/\//.test(href),
381
- })
382
- ````
383
-
384
- ## Commands
385
-
386
- ### editHyperLinkText(), editHyperLinkHref(), editHyperlink()
387
-
388
- These commands allow you to edit the text and href value of a hyperlink.
389
-
390
- ```js
391
- this.editor.commands.editHyperLinkText('New Text');
392
- this.editor.commands.editHyperLinkHref('https://new-url.com');
393
- this.editor.commands.editHyperlink({
394
- newText: 'New Text',
395
- newURL: 'https://new-url.com'
396
- });
397
- ```
398
-
399
- ### setHyperlink()
400
-
401
- Links the selected text.
402
-
403
- ````js
404
- this.editor.commands.setHyperlink({
405
- href: '<https://example.com>'
406
- });
407
-
408
- this.editor.commands.setHyperlink({
409
- href: '<https://example.com>',
410
- target: '_blank'
411
- });
412
-
413
- this.editor.commands.unsetHyperlink();
414
- ````
415
-
416
- ### unsetHyperlink()
417
-
418
- Removes a Hyperlink.
419
-
420
- ````js
421
- this.editor.commands.unsetHyperlink();
422
- ````
423
-
424
- ## Keyboard shortcuts
164
+ <details>
165
+ <summary><strong>Edit</strong> — preview → Edit</summary>
425
166
 
426
- Doesn’t have a keyboard shortcut
427
- This extension doesn’t bind a specific keyboard shortcut. You would probably open your custom UI on `Mod-k` though.
167
+ <p align="center">
168
+ <picture>
169
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/edit-dark.png">
170
+ <img alt="Edit-link popover with URL and text fields and Apply button" width="640" src="https://raw.githubusercontent.com/docs-plus/docs.plus/main/extensions/extension-hyperlink/assets/edit-light.png">
171
+ </picture>
172
+ </p>
428
173
 
429
- ## Get the current value
174
+ </details>
430
175
 
431
- Did you know that you can use `getAttributes` to find out which attributes, for example which href, is currently set? Don’t confuse it with a <u>command</u> (which changes the state), it’s just a method. Here is how that could look like:
176
+ ## Documentation
432
177
 
433
- ```js
434
- this.editor.getAttributes('link').href
435
- ```
178
+ | Guide | What it covers |
179
+ | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
180
+ | [API reference](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/api.md) | Options, commands, keyboard shortcuts, the full caveats list, server bundles, and TypeScript exports |
181
+ | [Popovers](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/popovers.md) | The three popover slots, factory option shapes, openers, and custom popovers |
182
+ | [Styling](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/styling.md) | The stylesheet, `--hl-*` theme tokens, dark mode, and class names |
183
+ | [URL handling](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/url-handling.md) | Href normalization, scheme classification, and Markdown round-trip |
184
+ | [Security](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/security.md) | The dangerous-scheme gate at each boundary, and the exported safety helpers |
185
+ | [Advanced](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/advanced.md) | The floating-popover primitive, the UI controller, and the tooltip primitive |
186
+ | [Migrating from 1.x](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/docs/migration.md) | Renamed options, commands, CSS classes, and popover APIs |
187
+ | [Changelog](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/CHANGELOG.md) | Every release and its breaking changes |
188
+ | [For AI coding agents](https://cdn.jsdelivr.net/npm/@docs.plus/extension-hyperlink/README.md) | This README as plain Markdown for your agent. The docs are also on [Context7](https://context7.com/docs-plus/docs.plus) |
436
189
 
437
- ## Sorce code and Example
190
+ ## Part of docs.plus
438
191
 
439
- - Demo:
440
- [extension-hyperlink](https://github.com/HMarzban/extension-hyperlink#test-drive-with-our-demo-)
441
- - Extension:
442
- [packages/extension-hyperlink](https://github.com/HMarzban/extension-hyperlink/tree/main/packages/extension-hyperlink)
443
- - Usage: [packages/nextjs/src/components/Tiptap.tsx](https://github.com/HMarzban/extension-hyperlink/blob/59f45eba1886202f4840eb2112c34574c16fe68a/packages/nextjs/src/components/Tiptap.tsx#L19-L29)
192
+ This extension is built for and maintained by [docs.plus](https://docs.plus). docs.plus is a free, real-time collaboration tool that lets communities organize knowledge hierarchically, with a chat thread on every heading. docs.plus runs these packages from source in production, so every release is exercised there before it reaches npm.
444
193
 
445
- ## Inspiration and Acknowledgment, Let's Connect
194
+ - Website: [docs.plus](https://docs.plus)
195
+ - Project README: [docs-plus/docs.plus](https://github.com/docs-plus/docs.plus#readme)
196
+ - Sibling extensions and recommended pairings: [extensions/README.md](https://github.com/docs-plus/docs.plus/blob/main/extensions/README.md)
446
197
 
447
- Thank you for exploring our Hyperlink extension from docs.plus! We aim to make collaboration and knowledge sharing not just easy, but also enjoyable.
198
+ ## Contributing
448
199
 
449
- Our extension is inspired by Tiptap's [extension-link](https://github.com/ueberdosis/tiptap/tree/main/packages/extension-link). While we've incorporated our own enhancements, we'd like to tip our hats to Tiptap for pioneering the "headless" approach that we admire greatly.
450
- > Please note: We're not affiliated with Tiptap, but we believe in recognizing foundational work.
200
+ Bug reports and PRs welcome. Setup, test commands, and the playground harness live in [CONTRIBUTING.md](https://github.com/docs-plus/docs.plus/blob/main/extensions/extension-hyperlink/CONTRIBUTING.md).
451
201
 
452
- Your feedback and interest in docs.plus are invaluable to us. Share your thoughts, suggestions, or dive deeper into our mission at the [docs.plus](https://github.com/docs-plus/docs.plus) repository.
202
+ ## License
453
203
 
454
- Wish to converse? Connect with us [here](https://github.com/docs-plus/docs.plus#-connect-with-us).
204
+ MIT — see [LICENSE](https://github.com/docs-plus/docs.plus/blob/main/LICENSE).