Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(files): create suggestions bar #6856

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
</template>
<ContentContainer v-show="contentLoaded"
ref="contentWrapper" />
<SuggestionsBar v-if="isRichEditor && isEmptyContent && contentLoaded" />
</MainContainer>
<Reader v-if="isResolvingConflict"
:content="syncError.data.outsideChange"
Expand Down Expand Up @@ -126,6 +127,7 @@
import Translate from './Modal/Translate.vue'
import { generateRemoteUrl } from '@nextcloud/router'
import { fetchNode } from '../services/WebdavClient.ts'
import SuggestionsBar from './SuggestionsBar.vue'

Check warning on line 130 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L130

Added line #L130 was not covered by tests

export default {
name: 'Editor',
Expand All @@ -141,6 +143,7 @@
Status,
Assistant,
Translate,
SuggestionsBar,

Check warning on line 146 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L146

Added line #L146 was not covered by tests
},
mixins: [
isMobile,
Expand Down Expand Up @@ -271,6 +274,7 @@
contentWrapper: null,
translateModal: false,
translateContent: '',
isEmptyContent: true,

Check warning on line 277 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L277

Added line #L277 was not covered by tests
}
},
computed: {
Expand Down Expand Up @@ -612,6 +616,11 @@
this.emit('update:content', {
markdown: proseMirrorMarkdown,
})
/**
* Empty document has an empty document and an empty paragraph (open and close blocks)
*/
const EMPTY_DOCUMENT_SIZE = 4
this.isEmptyContent = editor.state.doc.nodeSize <= EMPTY_DOCUMENT_SIZE

Check warning on line 623 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L619-L623

Added lines #L619 - L623 were not covered by tests
},

onSync({ steps, document }) {
Expand Down
46 changes: 4 additions & 42 deletions src/components/Menu/ActionInsertLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -76,6 +75,7 @@
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',
Expand Down Expand Up @@ -122,12 +122,7 @@
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)

Check warning on line 125 in src/components/Menu/ActionInsertLink.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/ActionInsertLink.vue#L125

Added line #L125 was not covered by tests

filePicker.pick()
.then((file) => {
Expand Down Expand Up @@ -173,42 +168,9 @@
* @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()

Check warning on line 171 in src/components/Menu/ActionInsertLink.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/ActionInsertLink.vue#L171

Added line #L171 was not covered by tests
},

/**
* Remove link markup at current position
* Triggered by the "remove link" button
Expand Down
156 changes: 156 additions & 0 deletions src/components/SuggestionsBar.vue
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'

Check warning on line 49 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L43-L49

Added lines #L43 - L49 were not covered by tests

export default {
name: 'SuggestionsBar',
components: {
Table,

Check failure on line 54 in src/components/SuggestionsBar.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Name "Table" is reserved in HTML
Document,
NcButton,
Shape,
},
mixins: [
useActionChooseLocalAttachmentMixin,
useEditorMixin,
useFileMixin,
],

Check warning on line 63 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L51-L63

Added lines #L51 - L63 were not covered by tests

data: () => {
return {
startPath: null,
}
},

Check warning on line 69 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L65-L69

Added lines #L65 - L69 were not covered by tests

computed: {
relativePath() {
return this.$file?.relativePath ?? '/'
},
},

Check warning on line 75 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L71-L75

Added lines #L71 - L75 were not covered by tests

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)
})
},

Check warning on line 95 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L77-L95

Added lines #L77 - L95 were not covered by tests

/**
* Insert table
* Triggered by the "Insert table" button
*/
insertTable() {
this.$editor.chain().focus().insertTable()?.run()
},

Check warning on line 103 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L97-L103

Added lines #L97 - L103 were not covered by tests

/**
* 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('/')
}

Check warning on line 112 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L105-L112

Added lines #L105 - L112 were not covered by tests

const filePicker = buildFilePicker(this.startPath)

Check warning on line 114 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L114

Added line #L114 was not covered by tests

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()
})
},

Check warning on line 129 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L116-L129

Added lines #L116 - L129 were not covered by tests

/**
* 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()
},
},

Check warning on line 141 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L131-L141

Added lines #L131 - L141 were not covered by tests

}

Check warning on line 143 in src/components/SuggestionsBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/SuggestionsBar.vue#L143

Added line #L143 was not covered by tests

</script>

<style scoped lang="scss">

.container-suggestions {
display: flex;
justify-content: center;
align-items: flex-end;
align-content: flex-end;
position: sticky;
}
</style>
15 changes: 15 additions & 0 deletions src/helpers/filePicker.js
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()
}

Check warning on line 15 in src/helpers/filePicker.js

View check run for this annotation

Codecov / codecov/patch

src/helpers/filePicker.js#L9-L15

Added lines #L9 - L15 were not covered by tests
45 changes: 45 additions & 0 deletions src/marks/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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'

Check failure on line 10 in src/marks/Link.js

View workflow job for this annotation

GitHub Actions / NPM lint

'getLinkWithPicker' is defined but never used

const PROTOCOLS_TO_LINK_TO = ['http:', 'https:', 'mailto:', 'tel:']

Expand Down Expand Up @@ -88,6 +89,50 @@
]
},

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()
}

Check warning on line 131 in src/marks/Link.js

View check run for this annotation

Codecov / codecov/patch

src/marks/Link.js#L101-L131

Added lines #L101 - L131 were not covered by tests
},
}
},

addProseMirrorPlugins() {
const plugins = this.parent()
// remove upstream link click handle plugin
Expand Down
Loading