-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: julia.kirschenheuter <[email protected]>
- Loading branch information
1 parent
6136301
commit d8a7b03
Showing
5 changed files
with
225 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,7 +66,6 @@ | |
<script> | ||
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 protected]") | ||
/^\//, // 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: [email protected]") | ||
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
<template> | ||
<div class="container-suggestions"> | ||
<NcButton ref="linkFileOrFolder" | ||
type="tertiary" | ||
size="normal" | ||
@click="linkFile"> | ||
<template #icon> | ||
<Document :size="20" /> | ||
</template> | ||
{{ t('text', 'Link to file or folder') }} | ||
</NcButton> | ||
|
||
<NcButton type="tertiary" | ||
size="normal" | ||
@click="$callChooseLocalAttachment"> | ||
<template #icon> | ||
<Document :size="20" /> | ||
</template> | ||
{{ t('text', 'Upload') }} | ||
</NcButton> | ||
|
||
<NcButton type="tertiary" | ||
size="normal" | ||
@click="insertTable"> | ||
<template #icon> | ||
<Table :size="20" /> | ||
</template> | ||
{{ t('text', 'Insert Table') }} | ||
</NcButton> | ||
|
||
<NcButton type="tertiary" | ||
size="normal" | ||
@click="linkPicker"> | ||
<template #icon> | ||
<Shape :size="20" /> | ||
</template> | ||
{{ t('text', 'Smart Picker') }} | ||
</NcButton> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { NcButton } from '@nextcloud/vue' | ||
import { Document, Table, Shape } from './icons.js' | ||
import { useActionChooseLocalAttachmentMixin } from './Editor/MediaHandler.provider.js' | ||
import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' | ||
import { useEditorMixin, useFileMixin } from './Editor.provider.js' | ||
import { generateUrl } from '@nextcloud/router' | ||
import { buildFilePicker } from '../helpers/filePicker.js' | ||
export default { | ||
name: 'SuggestionsBar', | ||
components: { | ||
Table, | ||
Document, | ||
NcButton, | ||
Shape, | ||
}, | ||
mixins: [ | ||
useActionChooseLocalAttachmentMixin, | ||
useEditorMixin, | ||
useFileMixin, | ||
], | ||
data: () => { | ||
return { | ||
startPath: null, | ||
} | ||
}, | ||
computed: { | ||
relativePath() { | ||
return this.$file?.relativePath ?? '/' | ||
}, | ||
}, | ||
methods: { | ||
/** | ||
* Open smart picker dialog | ||
* Triggered by the "Smart Picker" button | ||
*/ | ||
linkPicker() { | ||
getLinkWithPicker(null, true) | ||
.then(link => { | ||
const chain = this.$editor.chain() | ||
if (this.$editor.view.state?.selection.empty) { | ||
chain.focus().insertPreview(link).run() | ||
} else { | ||
chain.setLink({ href: link }).focus().run() | ||
} | ||
}) | ||
.catch(error => { | ||
console.error('Smart picker promise rejected', error) | ||
}) | ||
}, | ||
/** | ||
* Insert table | ||
* Triggered by the "Insert table" button | ||
*/ | ||
insertTable() { | ||
this.$editor.chain().focus().insertTable()?.run() | ||
}, | ||
/** | ||
* Open dialog and ask user which file to link to | ||
* Triggered by the "link to file or folder" button | ||
*/ | ||
linkFile() { | ||
if (this.startPath === null) { | ||
this.startPath = this.relativePath.split('/').slice(0, -1).join('/') | ||
} | ||
const filePicker = buildFilePicker(this.startPath) | ||
filePicker.pick() | ||
.then((file) => { | ||
const client = OC.Files.getClient() | ||
client.getFileInfo(file).then((_status, fileInfo) => { | ||
const url = new URL(generateUrl(`/f/${fileInfo.id}`), window.origin) | ||
this.setLink(url.href, fileInfo.name) | ||
this.startPath = fileInfo.path + (fileInfo.type === 'dir' ? `/${fileInfo.name}/` : '') | ||
}) | ||
}) | ||
.catch(() => { | ||
// do not close menu but keep focus | ||
this.$refs.linkFileOrFolder.$el.focus() | ||
}) | ||
}, | ||
/** | ||
* Save user entered URL as a link markup | ||
* Triggered when the user submits the ActionInput | ||
* | ||
* @param {string} url href attribute of the link | ||
* @param {string} text Text part of the link | ||
*/ | ||
setLink(url, text) { | ||
this.$editor.chain().setOrInsertLink(url, text).focus().run() | ||
}, | ||
}, | ||
} | ||
</script> | ||
<style scoped lang="scss"> | ||
.container-suggestions { | ||
display: flex; | ||
justify-content: center; | ||
align-items: flex-end; | ||
align-content: flex-end; | ||
position: sticky; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 protected]") | ||
/^\//, // 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: [email protected]") | ||
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 | ||
|