Linter
This commit is contained in:
parent
b3979008ae
commit
cc6eba7df1
16 changed files with 260 additions and 237 deletions
|
|
@ -119,9 +119,7 @@
|
|||
onmouseenter={() => (hoveredIndex = index)}
|
||||
onmouseleave={() => (hoveredIndex = null)}
|
||||
>
|
||||
<item.icon
|
||||
class="nav-icon {hoveredIndex === index ? 'animate' : ''}"
|
||||
/>
|
||||
<item.icon class="nav-icon {hoveredIndex === index ? 'animate' : ''}" />
|
||||
<span>{item.text}</span>
|
||||
</a>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,11 @@
|
|||
}
|
||||
|
||||
// Handle link context menu
|
||||
const handleShowLinkContextMenu = (pos: number, url: string, coords: { x: number, y: number }) => {
|
||||
const handleShowLinkContextMenu = (
|
||||
pos: number,
|
||||
url: string,
|
||||
coords: { x: number; y: number }
|
||||
) => {
|
||||
if (!editor) return
|
||||
|
||||
linkContextMenuPosition = { x: coords.x, y: coords.y + 5 }
|
||||
|
|
@ -325,7 +329,13 @@
|
|||
}
|
||||
|
||||
$effect(() => {
|
||||
if (showTextStyleDropdown || showMediaDropdown || showUrlConvertDropdown || showLinkContextMenu || showLinkEditDialog) {
|
||||
if (
|
||||
showTextStyleDropdown ||
|
||||
showMediaDropdown ||
|
||||
showUrlConvertDropdown ||
|
||||
showLinkContextMenu ||
|
||||
showLinkEditDialog
|
||||
) {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
|
|
@ -484,9 +494,9 @@
|
|||
// Dismiss URL convert dropdown if user types
|
||||
if (showUrlConvertDropdown && transaction.docChanged) {
|
||||
// Check if the change is actual typing (not just cursor movement)
|
||||
const hasTextChange = transaction.steps.some(step =>
|
||||
step.toJSON().stepType === 'replace' ||
|
||||
step.toJSON().stepType === 'replaceAround'
|
||||
const hasTextChange = transaction.steps.some(
|
||||
(step) =>
|
||||
step.toJSON().stepType === 'replace' || step.toJSON().stepType === 'replaceAround'
|
||||
)
|
||||
if (hasTextChange) {
|
||||
showUrlConvertDropdown = false
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Extension } from '@tiptap/core'
|
|||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
|
||||
export interface LinkContextMenuOptions {
|
||||
onShowContextMenu?: (pos: number, url: string, coords: { x: number, y: number }) => void
|
||||
onShowContextMenu?: (pos: number, url: string, coords: { x: number; y: number }) => void
|
||||
}
|
||||
|
||||
export const LinkContextMenu = Extension.create<LinkContextMenuOptions>({
|
||||
|
|
@ -30,7 +30,7 @@ export const LinkContextMenu = Extension.create<LinkContextMenuOptions>({
|
|||
|
||||
const $pos = state.doc.resolve(pos.pos)
|
||||
const marks = $pos.marks()
|
||||
const linkMark = marks.find(mark => mark.type.name === 'link')
|
||||
const linkMark = marks.find((mark) => mark.type.name === 'link')
|
||||
|
||||
if (linkMark && linkMark.attrs.href) {
|
||||
event.preventDefault()
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ export const UrlEmbed = Node.create<UrlEmbedOptions>({
|
|||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['div', mergeAttributes({ 'data-url-embed': '' }, this.options.HTMLAttributes, HTMLAttributes)]
|
||||
return [
|
||||
'div',
|
||||
mergeAttributes({ 'data-url-embed': '' }, this.options.HTMLAttributes, HTMLAttributes)
|
||||
]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
|
@ -106,7 +109,7 @@ export const UrlEmbed = Node.create<UrlEmbedOptions>({
|
|||
// Find the link mark at the given position
|
||||
const $pos = doc.resolve(pos)
|
||||
const marks = $pos.marks()
|
||||
const linkMark = marks.find(mark => mark.type.name === 'link')
|
||||
const linkMark = marks.find((mark) => mark.type.name === 'link')
|
||||
|
||||
if (!linkMark) return false
|
||||
|
||||
|
|
@ -119,14 +122,20 @@ export const UrlEmbed = Node.create<UrlEmbedOptions>({
|
|||
|
||||
// Walk backwards to find the start
|
||||
doc.nodesBetween(Math.max(0, pos - 300), pos, (node, nodePos) => {
|
||||
if (node.isText && node.marks.some(m => m.type.name === 'link' && m.attrs.href === url)) {
|
||||
if (
|
||||
node.isText &&
|
||||
node.marks.some((m) => m.type.name === 'link' && m.attrs.href === url)
|
||||
) {
|
||||
from = nodePos
|
||||
}
|
||||
})
|
||||
|
||||
// Walk forwards to find the end
|
||||
doc.nodesBetween(pos, Math.min(doc.content.size, pos + 300), (node, nodePos) => {
|
||||
if (node.isText && node.marks.some(m => m.type.name === 'link' && m.attrs.href === url)) {
|
||||
if (
|
||||
node.isText &&
|
||||
node.marks.some((m) => m.type.name === 'link' && m.attrs.href === url)
|
||||
) {
|
||||
to = nodePos + node.nodeSize
|
||||
}
|
||||
})
|
||||
|
|
@ -179,7 +188,8 @@ export const UrlEmbed = Node.create<UrlEmbedOptions>({
|
|||
// Check if it's a plain text paste
|
||||
if (text && !html) {
|
||||
// Simple URL regex check
|
||||
const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/
|
||||
const urlRegex =
|
||||
/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/
|
||||
|
||||
if (urlRegex.test(text.trim())) {
|
||||
// It's a URL, let it paste as a link naturally (don't prevent default)
|
||||
|
|
@ -199,20 +209,32 @@ export const UrlEmbed = Node.create<UrlEmbedOptions>({
|
|||
let linkEnd = -1
|
||||
|
||||
// Search for the link in a reasonable range
|
||||
for (let pos = beforePos; pos < Math.min(doc.content.size, beforePos + pastedUrl.length + 10); pos++) {
|
||||
for (
|
||||
let pos = beforePos;
|
||||
pos < Math.min(doc.content.size, beforePos + pastedUrl.length + 10);
|
||||
pos++
|
||||
) {
|
||||
try {
|
||||
const $pos = doc.resolve(pos)
|
||||
const marks = $pos.marks()
|
||||
const linkMark = marks.find(m => m.type.name === 'link' && m.attrs.href === pastedUrl)
|
||||
const linkMark = marks.find(
|
||||
(m) => m.type.name === 'link' && m.attrs.href === pastedUrl
|
||||
)
|
||||
|
||||
if (linkMark) {
|
||||
// Found the link, now find its boundaries
|
||||
linkStart = pos
|
||||
|
||||
// Find the end of the link
|
||||
for (let endPos = pos; endPos < Math.min(doc.content.size, pos + pastedUrl.length + 5); endPos++) {
|
||||
for (
|
||||
let endPos = pos;
|
||||
endPos < Math.min(doc.content.size, pos + pastedUrl.length + 5);
|
||||
endPos++
|
||||
) {
|
||||
const $endPos = doc.resolve(endPos)
|
||||
const hasLink = $endPos.marks().some(m => m.type.name === 'link' && m.attrs.href === pastedUrl)
|
||||
const hasLink = $endPos
|
||||
.marks()
|
||||
.some((m) => m.type.name === 'link' && m.attrs.href === pastedUrl)
|
||||
if (hasLink) {
|
||||
linkEnd = endPos + 1
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,17 @@
|
|||
onDismiss: () => void
|
||||
}
|
||||
|
||||
let { x, y, url, onConvertToLink, onCopyLink, onRefresh, onOpenLink, onRemove, onDismiss }: Props = $props()
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
url,
|
||||
onConvertToLink,
|
||||
onCopyLink,
|
||||
onRefresh,
|
||||
onOpenLink,
|
||||
onRemove,
|
||||
onDismiss
|
||||
}: Props = $props()
|
||||
|
||||
let dropdown: HTMLDivElement
|
||||
|
||||
|
|
@ -52,27 +62,17 @@
|
|||
<div class="menu-url">{url}</div>
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<button class="menu-item" onclick={onOpenLink}>
|
||||
Open link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onOpenLink}> Open link </button>
|
||||
|
||||
<button class="menu-item" onclick={onCopyLink}>
|
||||
Copy link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onCopyLink}> Copy link </button>
|
||||
|
||||
<button class="menu-item" onclick={onRefresh}>
|
||||
Refresh preview
|
||||
</button>
|
||||
<button class="menu-item" onclick={onRefresh}> Refresh preview </button>
|
||||
|
||||
<button class="menu-item" onclick={onConvertToLink}>
|
||||
Convert to link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onConvertToLink}> Convert to link </button>
|
||||
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<button class="menu-item danger" onclick={onRemove}>
|
||||
Remove card
|
||||
</button>
|
||||
<button class="menu-item danger" onclick={onRemove}> Remove card </button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,17 @@
|
|||
onDismiss: () => void
|
||||
}
|
||||
|
||||
let { x, y, url, onConvertToCard, onEditLink, onCopyLink, onRemoveLink, onOpenLink, onDismiss }: Props = $props()
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
url,
|
||||
onConvertToCard,
|
||||
onEditLink,
|
||||
onCopyLink,
|
||||
onRemoveLink,
|
||||
onOpenLink,
|
||||
onDismiss
|
||||
}: Props = $props()
|
||||
|
||||
let dropdown: HTMLDivElement
|
||||
|
||||
|
|
@ -52,27 +62,17 @@
|
|||
<div class="menu-url">{url}</div>
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<button class="menu-item" onclick={onOpenLink}>
|
||||
Open link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onOpenLink}> Open link </button>
|
||||
|
||||
<button class="menu-item" onclick={onEditLink}>
|
||||
Edit link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onEditLink}> Edit link </button>
|
||||
|
||||
<button class="menu-item" onclick={onCopyLink}>
|
||||
Copy link
|
||||
</button>
|
||||
<button class="menu-item" onclick={onCopyLink}> Copy link </button>
|
||||
|
||||
<button class="menu-item" onclick={onConvertToCard}>
|
||||
Convert to card
|
||||
</button>
|
||||
<button class="menu-item" onclick={onConvertToCard}> Convert to card </button>
|
||||
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<button class="menu-item danger" onclick={onRemoveLink}>
|
||||
Remove link
|
||||
</button>
|
||||
<button class="menu-item danger" onclick={onRemoveLink}> Remove link </button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -79,19 +79,10 @@
|
|||
class:invalid={urlInput && !isValid}
|
||||
/>
|
||||
<div class="dialog-actions">
|
||||
<button
|
||||
class="action-button save"
|
||||
onclick={handleSave}
|
||||
disabled={!isValid}
|
||||
title="Save"
|
||||
>
|
||||
<button class="action-button save" onclick={handleSave} disabled={!isValid} title="Save">
|
||||
<Check />
|
||||
</button>
|
||||
<button
|
||||
class="action-button cancel"
|
||||
onclick={onCancel}
|
||||
title="Cancel"
|
||||
>
|
||||
<button class="action-button cancel" onclick={onCancel} title="Cancel">
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -53,9 +53,7 @@
|
|||
transition:fly={{ y: -10, duration: 200 }}
|
||||
tabindex="-1"
|
||||
>
|
||||
<button class="convert-button" onclick={handleConvert}>
|
||||
Convert to card
|
||||
</button>
|
||||
<button class="convert-button" onclick={handleConvert}> Convert to card </button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@
|
|||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContentAt(
|
||||
{ from: pos, to: pos + node.nodeSize },
|
||||
[
|
||||
.insertContentAt({ from: pos, to: pos + node.nodeSize }, [
|
||||
{
|
||||
type: 'urlEmbed',
|
||||
attrs: {
|
||||
|
|
@ -50,8 +48,7 @@
|
|||
{
|
||||
type: 'paragraph'
|
||||
}
|
||||
]
|
||||
)
|
||||
])
|
||||
.run()
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -126,7 +123,13 @@
|
|||
<AlertCircle class="placeholder-icon" />
|
||||
<div class="error-content">
|
||||
<span class="placeholder-text">{errorMessage}</span>
|
||||
<button onclick={() => { showInput = true; error = false; }} class="retry-button">
|
||||
<button
|
||||
onclick={() => {
|
||||
showInput = true
|
||||
error = false
|
||||
}}
|
||||
class="retry-button"
|
||||
>
|
||||
Try another URL
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -201,7 +201,8 @@ function renderTiptapContent(doc: any): string {
|
|||
embedHtml += '<div class="youtube-embed-wrapper">'
|
||||
embedHtml += `<iframe src="https://www.youtube.com/embed/${videoId}" `
|
||||
embedHtml += 'frameborder="0" '
|
||||
embedHtml += 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" '
|
||||
embedHtml +=
|
||||
'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" '
|
||||
embedHtml += 'allowfullscreen>'
|
||||
embedHtml += '</iframe>'
|
||||
embedHtml += '</div>'
|
||||
|
|
|
|||
Loading…
Reference in a new issue