From ef05e27e8b6cf9ac25b532065fff26715c925801 Mon Sep 17 00:00:00 2001 From: "julia.kirschenheuter" Date: Fri, 31 Jan 2025 15:35:27 +0100 Subject: [PATCH] fix(files): add suggestions bar Signed-off-by: julia.kirschenheuter --- src/components/Editor.vue | 5 + src/components/Menu/ActionInsertLink.vue | 46 +------ src/components/SuggestionsBar.vue | 156 +++++++++++++++++++++++ src/helpers/filePicker.js | 15 +++ src/marks/Link.js | 45 +++++++ 5 files changed, 225 insertions(+), 42 deletions(-) create mode 100644 src/components/SuggestionsBar.vue create mode 100644 src/helpers/filePicker.js diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 76893fca873..a3df9cdb451 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -58,6 +58,7 @@ + import { NcActions, NcActionButton, NcActionInput } from '@nextcloud/vue' import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' -import { FilePickerType, getFilePickerBuilder } from '@nextcloud/dialogs' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' @@ -76,6 +75,7 @@ import { Document, Loading, LinkOff, Web, Shape } from '../icons.js' import { BaseActionEntry } from './BaseActionEntry.js' import { useFileMixin } from '../Editor.provider.js' import { useMenuIDMixin } from './MenuBar.provider.js' +import { buildFilePicker } from '../../helpers/filePicker.js' export default { name: 'ActionInsertLink', @@ -122,12 +122,7 @@ export default { this.startPath = this.relativePath.split('/').slice(0, -1).join('/') } - const filePicker = getFilePickerBuilder(t('text', 'Select file or folder to link to')) - .startAt(this.startPath) - .allowDirectories(true) - .setMultiSelect(false) - .setType(FilePickerType.Choose) - .build() + const filePicker = buildFilePicker(this.startPath) filePicker.pick() .then((file) => { @@ -173,42 +168,9 @@ export default { * @param {string} text Text part of the link */ setLink(url, text) { - // Heuristics for determining if we need a https:// prefix. - const noPrefixes = [ - /^[a-zA-Z]+:/, // url with protocol ("mailTo:email@domain.tld") - /^\//, // absolute path - /\?fileId=/, // relative link with fileId - /^\.\.?\//, // relative link starting with ./ or ../ - /^[^.]*[/$]/, // no dots before first '/' - not a domain name - /^#/, // url fragment - ] - if (url && !noPrefixes.find(regex => url.match(regex))) { - url = 'https://' + url - } - - // Avoid issues when parsing urls later on in markdown that might be entered in an invalid format (e.g. "mailto: example@example.com") - const href = url.replaceAll(' ', '%20') - const chain = this.$editor.chain() - // Check if any text is selected, if not insert the link using the given text property - if (this.$editor.view.state?.selection.empty) { - chain.insertContent({ - type: 'paragraph', - content: [{ - type: 'text', - marks: [{ - type: 'link', - attrs: { - href, - }, - }], - text, - }], - }) - } else { - chain.setLink({ href }) - } - chain.focus().run() + this.$editor.chain().setOrInsertLink(url, text).focus().run() }, + /** * Remove link markup at current position * Triggered by the "remove link" button diff --git a/src/components/SuggestionsBar.vue b/src/components/SuggestionsBar.vue new file mode 100644 index 00000000000..5f966e35f9c --- /dev/null +++ b/src/components/SuggestionsBar.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/src/helpers/filePicker.js b/src/helpers/filePicker.js new file mode 100644 index 00000000000..497e87b1079 --- /dev/null +++ b/src/helpers/filePicker.js @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { FilePickerType, getFilePickerBuilder } from '@nextcloud/dialogs' + +export const buildFilePicker = (startPath) => { + return getFilePickerBuilder(t('text', 'Select file or folder to link to')) + .startAt(startPath) + .allowDirectories(true) + .setMultiSelect(false) + .setType(FilePickerType.Choose) + .build() +} diff --git a/src/marks/Link.js b/src/marks/Link.js index 1878f43958b..6a2259018b1 100644 --- a/src/marks/Link.js +++ b/src/marks/Link.js @@ -7,6 +7,7 @@ import { markInputRule } from '@tiptap/core' import TipTapLink from '@tiptap/extension-link' import { domHref, parseHref } from './../helpers/links.js' import { linkClicking } from '../plugins/links.js' +import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' const PROTOCOLS_TO_LINK_TO = ['http:', 'https:', 'mailto:', 'tel:'] @@ -88,6 +89,50 @@ const Link = TipTapLink.extend({ ] }, + addCommands() { + return { + /** + * Check if any text is selected, if not insert the link using the given text property + * + * @param {string} url href attribute of the link + * @param {string} text Text part of the link + */ + setOrInsertLink: (url, text) => ({ state, chain }) => { + // Heuristics for determining if we need a https:// prefix. + const noPrefixes = [ + /^[a-zA-Z]+:/, // url with protocol ("mailTo:email@domain.tld") + /^\//, // absolute path + /\?fileId=/, // relative link with fileId + /^\.\.?\//, // relative link starting with ./ or ../ + /^[^.]*[/$]/, // no dots before first '/' - not a domain name + /^#/, // url fragment + ] + if (url && !noPrefixes.find(regex => url.match(regex))) { + url = 'https://' + url + } + // Avoid issues when parsing urls later on in markdown that might be entered in an invalid format (e.g. "mailto: example@example.com") + const href = url.replaceAll(' ', '%20') + if (state.selection.empty) { + return chain().insertContent({ + type: 'paragraph', + content: [{ + type: 'text', + marks: [{ + type: 'link', + attrs: { + href, + }, + }], + text, + }], + }).run() + } else { + return chain().setLink({ href }).run() + } + }, + } + }, + addProseMirrorPlugins() { const plugins = this.parent() // remove upstream link click handle plugin