apropiadamente implementar nuevo linkeo

This commit is contained in:
Cat /dev/Nulo 2023-08-31 15:02:58 -03:00
parent 15ffbae34d
commit 2b44ab6102
7 changed files with 88 additions and 105 deletions

View file

@ -33,7 +33,7 @@
"prosemirror-schema-list": "~1.2.2", "prosemirror-schema-list": "~1.2.2",
"prosemirror-state": "~1.4.2", "prosemirror-state": "~1.4.2",
"prosemirror-transform": "~1.7.1", "prosemirror-transform": "~1.7.1",
"prosemirror-view": "~1.29.2", "prosemirror-view": "^1.31.7",
"svelte": "^3.58.0", "svelte": "^3.58.0",
"svelte-check": "^2.10.3", "svelte-check": "^2.10.3",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",

View file

@ -31,7 +31,7 @@ dependencies:
version: 9.0.10(yjs@13.5.52) version: 9.0.10(yjs@13.5.52)
y-prosemirror: y-prosemirror:
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1(prosemirror-model@1.18.3)(prosemirror-state@1.4.2)(prosemirror-view@1.29.2)(y-protocols@1.0.5)(yjs@13.5.52) version: 1.2.1(prosemirror-model@1.18.3)(prosemirror-state@1.4.2)(prosemirror-view@1.31.7)(y-protocols@1.0.5)(yjs@13.5.52)
y-protocols: y-protocols:
specifier: ^1.0.5 specifier: ^1.0.5
version: 1.0.5 version: 1.0.5
@ -107,8 +107,8 @@ devDependencies:
specifier: ~1.7.1 specifier: ~1.7.1
version: 1.7.1 version: 1.7.1
prosemirror-view: prosemirror-view:
specifier: ~1.29.2 specifier: ^1.31.7
version: 1.29.2 version: 1.31.7
svelte: svelte:
specifier: ^3.58.0 specifier: ^3.58.0
version: 3.58.0 version: 3.58.0
@ -595,7 +595,7 @@ packages:
normalize-path: 3.0.0 normalize-path: 3.0.0
readdirp: 3.6.0 readdirp: 3.6.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.3
dev: true dev: true
/commander@4.1.1: /commander@4.1.1:
@ -796,8 +796,8 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
/fsevents@2.3.2: /fsevents@2.3.3:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
@ -1307,7 +1307,7 @@ packages:
dependencies: dependencies:
prosemirror-state: 1.4.2 prosemirror-state: 1.4.2
prosemirror-transform: 1.7.1 prosemirror-transform: 1.7.1
prosemirror-view: 1.29.2 prosemirror-view: 1.31.7
dev: true dev: true
/prosemirror-gapcursor@1.3.1: /prosemirror-gapcursor@1.3.1:
@ -1316,7 +1316,7 @@ packages:
prosemirror-keymap: 1.2.1 prosemirror-keymap: 1.2.1
prosemirror-model: 1.18.3 prosemirror-model: 1.18.3
prosemirror-state: 1.4.2 prosemirror-state: 1.4.2
prosemirror-view: 1.29.2 prosemirror-view: 1.31.7
dev: true dev: true
/prosemirror-history@1.3.0: /prosemirror-history@1.3.0:
@ -1371,15 +1371,15 @@ packages:
dependencies: dependencies:
prosemirror-model: 1.18.3 prosemirror-model: 1.18.3
prosemirror-transform: 1.7.1 prosemirror-transform: 1.7.1
prosemirror-view: 1.29.2 prosemirror-view: 1.31.7
/prosemirror-transform@1.7.1: /prosemirror-transform@1.7.1:
resolution: {integrity: sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==} resolution: {integrity: sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==}
dependencies: dependencies:
prosemirror-model: 1.18.3 prosemirror-model: 1.18.3
/prosemirror-view@1.29.2: /prosemirror-view@1.31.7:
resolution: {integrity: sha512-T4Wm+eTpTH0N9gBJfJR6iecjRX2hYTKewoJUwa92hQOoEz2bYVZy6sYeN+hfnRR506TRvRcuZYqftp4KA8dN+Q==} resolution: {integrity: sha512-Pr7w93yOYmxQwzGIRSaNLZ/1uM6YjnenASzN2H6fO6kGekuzRbgZ/4bHbBTd1u4sIQmL33/TcGmzxxidyPwCjg==}
dependencies: dependencies:
prosemirror-model: 1.18.3 prosemirror-model: 1.18.3
prosemirror-state: 1.4.2 prosemirror-state: 1.4.2
@ -1457,7 +1457,7 @@ packages:
engines: {node: '>=14.18.0', npm: '>=8.0.0'} engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.3
dev: true dev: true
/rope-sequence@1.3.3: /rope-sequence@1.3.3:
@ -1797,7 +1797,7 @@ packages:
resolve: 1.22.2 resolve: 1.22.2
rollup: 3.20.6 rollup: 3.20.6
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.3
dev: true dev: true
/vitefu@0.2.4(vite@4.2.2): /vitefu@0.2.4(vite@4.2.2):
@ -1843,7 +1843,7 @@ packages:
yjs: 13.5.52 yjs: 13.5.52
dev: false dev: false
/y-prosemirror@1.2.1(prosemirror-model@1.18.3)(prosemirror-state@1.4.2)(prosemirror-view@1.29.2)(y-protocols@1.0.5)(yjs@13.5.52): /y-prosemirror@1.2.1(prosemirror-model@1.18.3)(prosemirror-state@1.4.2)(prosemirror-view@1.31.7)(y-protocols@1.0.5)(yjs@13.5.52):
resolution: {integrity: sha512-czMBfB1eL2awqmOSxQM8cS/fsUOGE6fjvyPLInrh4crPxFiw67wDpwIW+EGBYKRa04sYbS0ScGj7ZgvWuDrmBQ==} resolution: {integrity: sha512-czMBfB1eL2awqmOSxQM8cS/fsUOGE6fjvyPLInrh4crPxFiw67wDpwIW+EGBYKRa04sYbS0ScGj7ZgvWuDrmBQ==}
peerDependencies: peerDependencies:
prosemirror-model: ^1.7.1 prosemirror-model: ^1.7.1
@ -1855,7 +1855,7 @@ packages:
lib0: 0.2.73 lib0: 0.2.73
prosemirror-model: 1.18.3 prosemirror-model: 1.18.3
prosemirror-state: 1.4.2 prosemirror-state: 1.4.2
prosemirror-view: 1.29.2 prosemirror-view: 1.31.7
y-protocols: 1.0.5 y-protocols: 1.0.5
yjs: 13.5.52 yjs: 13.5.52
dev: false dev: false

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount, tick } from "svelte"; import { onDestroy, onMount, tick } from "svelte";
import type { Writable } from "svelte/store";
import type { EditorView } from "prosemirror-view"; import type { EditorView } from "prosemirror-view";
import { EditorState, TextSelection } from "prosemirror-state"; import { EditorState, TextSelection } from "prosemirror-state";
@ -17,6 +18,7 @@
removeMark, removeMark,
markIsActive, markIsActive,
getFirstMarkInSelection, getFirstMarkInSelection,
selectMark,
} from "./ps-utils"; } from "./ps-utils";
import SimpleMarkItem from "./bubblemenu/SimpleMarkItem.svelte"; import SimpleMarkItem from "./bubblemenu/SimpleMarkItem.svelte";
import Button from "./bubblemenu/Button.svelte"; import Button from "./bubblemenu/Button.svelte";
@ -28,6 +30,7 @@
export let view: EditorView; export let view: EditorView;
export let state: EditorState; export let state: EditorState;
export let worldY: WorldY; export let worldY: WorldY;
export let editingLink: Writable<false | "new" | "selection">;
let changingProp: let changingProp:
| false | false
@ -55,36 +58,19 @@
} }
function startEditingLink() { function startEditingLink() {
const match = getFirstMarkInSelection(state, view.state.schema.marks.link); const match = getFirstMarkInSelection(
view.state,
// si no hay un link en la selección, empezar a editar uno sin ningún enlace view.state.schema.marks.link,
// TODO: quizás queremos poner algo tipo https://sutty.nl por defecto?
if (!match) {
changingProp = { type: "link", url: "" };
return;
}
view.dispatch(
state.tr.setSelection(
TextSelection.create(
state.doc,
match.position,
match.position + match.node.nodeSize
)
)
); );
changingProp = { type: "link", url: match.mark.attrs.href }; if (match) {
} selectMark(match, view.state, view.dispatch);
$editingLink = "selection";
function removeLink() { } else if (!view.state.selection.empty) {
changingProp = false; $editingLink = "selection";
runCommand(removeMark(view.state.schema.marks.link)); } else {
} runCommand(removeMark(view.state.schema.marks.link));
$editingLink = "new";
function onChangeLink(event: Event) { }
changingProp = false;
const url = (event.target as HTMLInputElement).value;
runCommand(updateMark(view.state.schema.marks.link, { href: url }));
} }
let makingInternalLink = false; let makingInternalLink = false;
@ -134,7 +120,7 @@ transform: scale(${1 / viewport.scale});
</Modal> </Modal>
{/if} {/if}
<div class="floating" style={barStyle}> <div class="floating z-40" style={barStyle}>
<Linking {state} /> <Linking {state} />
<div class="bubble" hidden={state.selection.empty}> <div class="bubble" hidden={state.selection.empty}>
{#if changingProp === false} {#if changingProp === false}
@ -162,17 +148,6 @@ transform: scale(${1 / viewport.scale});
onClick={startMakingInternalLink} onClick={startMakingInternalLink}
><InternalLinkIcon style={svgStyle} /></Button ><InternalLinkIcon style={svgStyle} /></Button
> >
{:else if changingProp.type === "link"}
<input
bind:this={linkInputEl}
type="text"
placeholder="https://"
on:change|preventDefault={onChangeLink}
value={changingProp.url}
/>
<Button title="Borrar enlace" onClick={removeLink}
><CloseIcon style={svgStyle} /></Button
>
{/if} {/if}
</div> </div>
</div> </div>
@ -202,19 +177,14 @@ transform: scale(${1 / viewport.scale});
opacity: 1; opacity: 1;
height: auto; height: auto;
transition: opacity 0.2s, visibility 0.2s, height 0.2s; transition:
opacity 0.2s,
visibility 0.2s,
height 0.2s;
} }
.bubble[hidden] { .bubble[hidden] {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
height: 0; height: 0;
} }
.bubble input {
appearance: none;
background: none;
color: inherit;
border: none;
font-size: 1.25em;
}
</style> </style>

View file

@ -83,7 +83,7 @@
bind:this={wrapperEl} bind:this={wrapperEl}
/> />
{#if view} {#if view}
<BubbleMenu {view} {worldY} state={updatedState} /> <BubbleMenu {view} {worldY} {editingLink} state={updatedState} />
{/if} {/if}
</div> </div>

View file

@ -4,8 +4,13 @@
import { getFirstMarkInSelection } from "../ps-utils"; import { getFirstMarkInSelection } from "../ps-utils";
import { readable, type Writable } from "svelte/store"; import { readable, type Writable } from "svelte/store";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { markSelectionFloatingUi } from "./floatingUi"; import { linkFloatingUi, selectionFloatingUi } from "./floatingUi";
import { autoPlacement, shift, offset } from "@floating-ui/dom"; import {
autoPlacement,
shift,
offset,
type ComputePositionConfig,
} from "@floating-ui/dom";
export let state: EditorState; export let state: EditorState;
export let view: EditorView; export let view: EditorView;
@ -76,15 +81,12 @@
$: shown = !!$editingLink; $: shown = !!$editingLink;
$: linkMatch = $: style = shown
state && getFirstMarkInSelection(view.state, view.state.schema.marks.link); ? selectionFloatingUi(formEl, {
$: style = placement: "top",
shown && linkMatch middleware: [offset(6), autoPlacement(), shift({ padding: 5 })],
? markSelectionFloatingUi(view, linkMatch, formEl, { })
placement: "top", : readable("");
middleware: [offset(6), autoPlacement(), shift({ padding: 5 })],
})
: readable("");
</script> </script>
<svelte:document on:pointerdown={detectFocus} /> <svelte:document on:pointerdown={detectFocus} />

View file

@ -3,11 +3,10 @@
import { import {
getFirstMarkInSelection, getFirstMarkInSelection,
getMarkRange, getMarkRange,
removeMark,
selectMark, selectMark,
} from "../ps-utils"; } from "../ps-utils";
import type { EditorView } from "prosemirror-view"; import type { EditorView } from "prosemirror-view";
import { markSelectionFloatingUi } from "./floatingUi"; import { linkFloatingUi, selectionFloatingUi } from "./floatingUi";
import { readable, type Writable } from "svelte/store"; import { readable, type Writable } from "svelte/store";
import { flip, shift, offset } from "@floating-ui/dom"; import { flip, shift, offset } from "@floating-ui/dom";
import EditIcon from "eva-icons/outline/svg/edit-outline.svg"; import EditIcon from "eva-icons/outline/svg/edit-outline.svg";
@ -22,13 +21,12 @@
$: link = state && getFirstMarkInSelection(view.state, markType); $: link = state && getFirstMarkInSelection(view.state, markType);
$: shown = !!link && !$editingLink; $: shown = !!link && !$editingLink;
$: style = $: style = shown
shown && link ? linkFloatingUi(view, tooltipEl, {
? markSelectionFloatingUi(view, link, tooltipEl, { placement: "bottom",
placement: "bottom", middleware: [offset(6), flip(), shift({ padding: 5 })],
middleware: [offset(6), flip(), shift({ padding: 5 })], })
}) : readable("");
: readable("");
function editLink() { function editLink() {
if (!link) return; if (!link) return;
@ -44,7 +42,7 @@
</script> </script>
<div <div
class="absolute z-30 w-max items-center overflow-hidden rounded border border-neutral-200/70 bg-white px-1 leading-none shadow-lg dark:border-neutral-700 dark:bg-neutral-900" class="absolute z-20 w-max items-center overflow-hidden rounded border border-neutral-200/70 bg-white px-1 leading-none shadow-lg dark:border-neutral-700 dark:bg-neutral-900"
class:flex={shown} class:flex={shown}
class:hidden={!shown} class:hidden={!shown}
bind:this={tooltipEl} bind:this={tooltipEl}

View file

@ -1,19 +1,16 @@
import { readable, type Readable } from "svelte/store"; import { readable, type Readable } from "svelte/store";
import { import { computePosition, autoUpdate } from "@floating-ui/dom";
computePosition, import type {
autoUpdate, ComputePositionConfig,
autoPlacement, ReferenceElement,
shift, } from "@floating-ui/dom/src/types";
offset,
} from "@floating-ui/dom";
import type { ComputePositionConfig } from "@floating-ui/dom/src/types";
import type { MarkMatch } from "../ps-utils";
import type { EditorView } from "prosemirror-view"; import type { EditorView } from "prosemirror-view";
import { getFirstMarkInSelection } from "../ps-utils";
export type Style = string; export type Style = string;
export function floatingUi( export function floatingUi(
refEl: Element, refEl: ReferenceElement,
tooltipEl: HTMLElement, tooltipEl: HTMLElement,
options?: Partial<ComputePositionConfig>, options?: Partial<ComputePositionConfig>,
): Readable<Style> { ): Readable<Style> {
@ -28,14 +25,30 @@ export function floatingUi(
}; };
} }
export function markSelectionFloatingUi( export function selectionFloatingUi(
view: EditorView,
mark: MarkMatch,
tooltipEl: HTMLElement, tooltipEl: HTMLElement,
options?: Partial<ComputePositionConfig>, options?: Partial<ComputePositionConfig>,
): Readable<Style> { ): Readable<Style> {
let { node } = (view as any).docView.domFromPos(view.state.selection.from); const sel = document.getSelection();
if (!node || !mark || !tooltipEl) return readable(""); const range = sel?.getRangeAt(0);
if (!(node instanceof Element)) node = node.parentElement;
return floatingUi(node, tooltipEl, options); if (!range) return readable("");
return floatingUi(range, tooltipEl, options);
}
export function linkFloatingUi(
view: EditorView,
tooltipEl: HTMLElement,
options?: Partial<ComputePositionConfig>,
): Readable<Style> {
const mark = getFirstMarkInSelection(
view.state,
view.state.schema.marks.link,
);
if (!mark) return readable("");
let node = view.nodeDOM(mark?.position);
if (!node) return readable("");
const element = node instanceof Element ? node : node.parentElement;
if (!element) return readable("");
return floatingUi(element, tooltipEl, options);
} }