diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 1b10ef91b..194832d5f 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -2,7 +2,7 @@ ## In progress -// +- Link heading-based ID reference menu ## Main Todo @@ -10,8 +10,6 @@ - Alignments: Handle inline block content (image, video) - Image paste upload - Keyboard shortcuts support -- Link popup menu for cross-content reference -- Link heading-based ID reference menu - Drawing gallery integration - Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts) - Media resize support (like images) diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index c37696695..6bd265e6c 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts @@ -5,9 +5,9 @@ import { EditorSelectFormFieldDefinition } from "../../framework/forms"; import {EditorUiContext} from "../../framework/core"; -import {$createTextNode, $getSelection} from "lexical"; +import {$createTextNode, $getSelection, $insertNodes} from "lexical"; import {$isImageNode, ImageNode} from "../../../nodes/image"; -import {$createLinkNode} from "@lexical/link"; +import {$createLinkNode, $isLinkNode} from "@lexical/link"; import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media"; import {$insertNodeToNearestRoot} from "@lexical/utils"; import {$getNodeFromSelection} from "../../../utils/selection"; @@ -16,6 +16,8 @@ import {EditorActionField} from "../../framework/blocks/action-field"; import {EditorButton} from "../../framework/buttons"; import {showImageManager} from "../../../utils/images"; import searchImageIcon from "@icons/editor/image-search.svg"; +import searchIcon from "@icons/search.svg"; +import {showLinkSelector} from "../../../utils/links"; export function $showImageForm(image: ImageNode, context: EditorUiContext) { const imageModal: EditorFormModal = context.manager.createModal('image'); @@ -97,23 +99,62 @@ export const link: EditorFormDefinition = { async action(formData, context: EditorUiContext) { context.editor.update(() => { + const url = formData.get('url')?.toString() || ''; + const title = formData.get('title')?.toString() || '' + const target = formData.get('target')?.toString() || ''; + const text = formData.get('text')?.toString() || ''; + const selection = $getSelection(); + let link = $getNodeFromSelection(selection, $isLinkNode); + if ($isLinkNode(link)) { + link.setURL(url); + link.setTarget(target); + link.setTitle(title); + } else { + link = $createLinkNode(url, { + title: title, + target: target, + }); - const linkNode = $createLinkNode(formData.get('url')?.toString() || '', { - title: formData.get('title')?.toString() || '', - target: formData.get('target')?.toString() || '', - }); - linkNode.append($createTextNode(formData.get('text')?.toString() || '')); + $insertNodes([link]); + } - selection?.insertNodes([linkNode]); + if ($isLinkNode(link)) { + for (const child of link.getChildren()) { + child.remove(true); + } + link.append($createTextNode(text)); + } }); return true; }, fields: [ { - label: 'URL', - name: 'url', - type: 'text', + build() { + return new EditorActionField( + new EditorFormField({ + label: 'URL', + name: 'url', + type: 'text', + }), + new EditorButton({ + label: 'Browse links', + icon: searchIcon, + action(context: EditorUiContext) { + showLinkSelector(entity => { + const modal = context.manager.getActiveModal('link'); + if (modal) { + modal.getForm().setValues({ + url: entity.link, + text: entity.name, + title: entity.name, + }); + } + }); + } + }), + ); + }, }, { label: 'Text to display', diff --git a/resources/js/wysiwyg/utils/links.ts b/resources/js/wysiwyg/utils/links.ts new file mode 100644 index 000000000..03c4a5ef0 --- /dev/null +++ b/resources/js/wysiwyg/utils/links.ts @@ -0,0 +1,16 @@ +import {EntitySelectorPopup} from "../../components"; + +type EditorEntityData = { + link: string; + name: string; +}; + +export function showLinkSelector(callback: (entity: EditorEntityData) => any, selectionText?: string) { + const selector: EntitySelectorPopup = window.$components.first('entity-selector-popup') as EntitySelectorPopup; + selector.show((entity: EditorEntityData) => callback(entity), { + initialValue: selectionText, + searchEndpoint: '/search/entity-selector', + entityTypes: 'page,book,chapter,bookshelf', + entityPermission: 'view', + }); +} \ No newline at end of file