@bagelink/vue 1.18.7 → 1.19.1
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/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/core.css +1 -1
- package/dist/index.cjs +41 -41
- package/dist/index.css +1 -1
- package/dist/index.mjs +6035 -6056
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/form/inputs/RichText/index.vue +28 -16
- package/dist/components/form/inputs/RichText/utils/linkPolicy.d.ts +0 -11
- package/dist/components/form/inputs/RichText/utils/linkPolicy.d.ts.map +0 -1
- package/src/components/form/inputs/RichText/utils/linkPolicy.ts +0 -48
package/package.json
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
import type { BagelInputShellProps } from '../bagelInputShell'
|
|
31
31
|
import { useBagelInputShell } from '../bagelInputShell'
|
|
32
32
|
import { RICH_TEXT_EXTENSION_REGISTRY_KEY } from './richTextExtensions'
|
|
33
|
-
import { normalizeRichTextLink, sanitizeRichTextLinks } from './utils/linkPolicy'
|
|
34
33
|
|
|
35
34
|
// Disable automatic inheritance of non-prop attributes
|
|
36
35
|
defineOptions({
|
|
@@ -697,7 +696,7 @@ function showTooltipMessage(message: string, x?: number, y?: number) {
|
|
|
697
696
|
function openLinkModal(selection: Selection, range: Range, existingLink: HTMLAnchorElement | null) {
|
|
698
697
|
pendingLinkData = { selection, range, existingLink }
|
|
699
698
|
linkForm.value = {
|
|
700
|
-
url: existingLink?.
|
|
699
|
+
url: existingLink?.href || '',
|
|
701
700
|
title: existingLink?.getAttribute('title') || '',
|
|
702
701
|
openInNewTab: existingLink?.target !== '_self'
|
|
703
702
|
}
|
|
@@ -706,19 +705,17 @@ function openLinkModal(selection: Selection, range: Range, existingLink: HTMLAnc
|
|
|
706
705
|
|
|
707
706
|
// Function to submit link
|
|
708
707
|
function submitLink() {
|
|
709
|
-
|
|
710
|
-
if (!pendingLinkData || !normalizedUrl) { return }
|
|
708
|
+
if (!pendingLinkData || !linkForm.value.url) { return }
|
|
711
709
|
|
|
712
710
|
const { selection, range, existingLink } = pendingLinkData
|
|
713
|
-
const { title, openInNewTab } = linkForm.value
|
|
711
|
+
const { url, title, openInNewTab } = linkForm.value
|
|
714
712
|
const { doc } = editor.state
|
|
715
713
|
|
|
716
714
|
if (!doc) { return }
|
|
717
|
-
linkForm.value.url = normalizedUrl
|
|
718
715
|
|
|
719
716
|
if (existingLink) {
|
|
720
717
|
// Update existing link
|
|
721
|
-
existingLink.
|
|
718
|
+
existingLink.href = url
|
|
722
719
|
if (title.trim()) {
|
|
723
720
|
existingLink.title = title.trim()
|
|
724
721
|
} else {
|
|
@@ -734,7 +731,7 @@ function submitLink() {
|
|
|
734
731
|
// Create new link
|
|
735
732
|
try {
|
|
736
733
|
const anchor = doc.createElement('a')
|
|
737
|
-
anchor.
|
|
734
|
+
anchor.href = url
|
|
738
735
|
if (title.trim()) {
|
|
739
736
|
anchor.title = title.trim()
|
|
740
737
|
}
|
|
@@ -769,8 +766,14 @@ function submitLink() {
|
|
|
769
766
|
|
|
770
767
|
// Function to visit link
|
|
771
768
|
function visitLink() {
|
|
772
|
-
|
|
773
|
-
|
|
769
|
+
if (!linkForm.value.url || !isValidUrl(linkForm.value.url)) { return }
|
|
770
|
+
|
|
771
|
+
let url = linkForm.value.url.trim()
|
|
772
|
+
|
|
773
|
+
// Add https:// if no protocol is specified
|
|
774
|
+
if (!url.match(/^https?:\/\//)) {
|
|
775
|
+
url = `https://${url}`
|
|
776
|
+
}
|
|
774
777
|
|
|
775
778
|
if (linkForm.value.openInNewTab) {
|
|
776
779
|
window.open(url, '_blank', 'noopener,noreferrer')
|
|
@@ -781,7 +784,20 @@ function visitLink() {
|
|
|
781
784
|
|
|
782
785
|
// Function to validate URL
|
|
783
786
|
function isValidUrl(url: string): boolean {
|
|
784
|
-
|
|
787
|
+
if (!url || url.trim() === '') { return false }
|
|
788
|
+
|
|
789
|
+
try {
|
|
790
|
+
URL.canParse(url)
|
|
791
|
+
return true
|
|
792
|
+
} catch {
|
|
793
|
+
// Try with https:// prefix if it's missing
|
|
794
|
+
try {
|
|
795
|
+
URL.canParse(`https://${url}`)
|
|
796
|
+
return true
|
|
797
|
+
} catch {
|
|
798
|
+
return false
|
|
799
|
+
}
|
|
800
|
+
}
|
|
785
801
|
}
|
|
786
802
|
|
|
787
803
|
// Function to open image modal
|
|
@@ -915,7 +931,7 @@ function submitImage() {
|
|
|
915
931
|
|
|
916
932
|
// Wrap the image in an anchor when a link is provided, otherwise return the bare image
|
|
917
933
|
function createImageContent(): HTMLElement {
|
|
918
|
-
const link =
|
|
934
|
+
const link = imageForm.value.link?.trim()
|
|
919
935
|
if (!link) { return img }
|
|
920
936
|
|
|
921
937
|
const anchor = doc!.createElement('a')
|
|
@@ -1985,10 +2001,6 @@ function getCleanHTML(doc: Document | null): string {
|
|
|
1985
2001
|
if (fig.classList.length === 0) { fig.removeAttribute('class') }
|
|
1986
2002
|
})
|
|
1987
2003
|
|
|
1988
|
-
// Keep imported/pasted legacy links from leaking into saved output. Invalid
|
|
1989
|
-
// destinations lose their href, but their visible content is preserved.
|
|
1990
|
-
sanitizeRichTextLinks(clone)
|
|
1991
|
-
|
|
1992
2004
|
const html = clone.innerHTML
|
|
1993
2005
|
if (!clone.textContent?.trim() && !clone.querySelector('img, video, iframe, table, hr, figure')) {
|
|
1994
2006
|
return ''
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validate and normalize a link authored in RichText.
|
|
3
|
-
*
|
|
4
|
-
* Relative application paths must be root-relative (`/contact`). Ambiguous
|
|
5
|
-
* imported CMS links (`edit?...`, `admin/...`) are rejected instead of being
|
|
6
|
-
* silently resolved against the current page.
|
|
7
|
-
*/
|
|
8
|
-
export declare function normalizeRichTextLink(value: unknown): string | null;
|
|
9
|
-
/** Remove invalid destinations while preserving their visible linked content. */
|
|
10
|
-
export declare function sanitizeRichTextLinks(root: ParentNode): number;
|
|
11
|
-
//# sourceMappingURL=linkPolicy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"linkPolicy.d.ts","sourceRoot":"","sources":["../../../../../../src/components/form/inputs/RichText/utils/linkPolicy.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAoBnE;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAc9D"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const EXPLICIT_SAFE_URL = /^(?:https?:\/\/|mailto:|tel:)/i
|
|
2
|
-
const BARE_DOMAIN = /^(?:localhost(?::\d+)?|(?:[a-z0-9-]+\.)+[a-z]{2,})(?:[/:?#][^\s]*)?$/i
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Validate and normalize a link authored in RichText.
|
|
6
|
-
*
|
|
7
|
-
* Relative application paths must be root-relative (`/contact`). Ambiguous
|
|
8
|
-
* imported CMS links (`edit?...`, `admin/...`) are rejected instead of being
|
|
9
|
-
* silently resolved against the current page.
|
|
10
|
-
*/
|
|
11
|
-
export function normalizeRichTextLink(value: unknown): string | null {
|
|
12
|
-
if (typeof value !== 'string') return null
|
|
13
|
-
const url = value.trim()
|
|
14
|
-
if (!url || /[\u0000-\u001F\u007F]/.test(url)) return null
|
|
15
|
-
|
|
16
|
-
if (url.startsWith('/') && !url.startsWith('//')) return url
|
|
17
|
-
if (url.startsWith('#') && url.length > 1) return url
|
|
18
|
-
|
|
19
|
-
if (EXPLICIT_SAFE_URL.test(url)) {
|
|
20
|
-
if (/^mailto:/i.test(url)) return url.slice(7).trim() ? url : null
|
|
21
|
-
if (/^tel:/i.test(url)) return url.slice(4).trim() ? url : null
|
|
22
|
-
try {
|
|
23
|
-
const parsed = new URL(url)
|
|
24
|
-
return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? url : null
|
|
25
|
-
} catch {
|
|
26
|
-
return null
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return BARE_DOMAIN.test(url) ? `https://${url}` : null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Remove invalid destinations while preserving their visible linked content. */
|
|
34
|
-
export function sanitizeRichTextLinks(root: ParentNode): number {
|
|
35
|
-
let removed = 0
|
|
36
|
-
for (const anchor of Array.from(root.querySelectorAll<HTMLAnchorElement>('a[href]'))) {
|
|
37
|
-
const normalized = normalizeRichTextLink(anchor.getAttribute('href'))
|
|
38
|
-
if (normalized) {
|
|
39
|
-
anchor.setAttribute('href', normalized)
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
anchor.removeAttribute('href')
|
|
43
|
-
anchor.removeAttribute('target')
|
|
44
|
-
anchor.removeAttribute('rel')
|
|
45
|
-
removed++
|
|
46
|
-
}
|
|
47
|
-
return removed
|
|
48
|
-
}
|