Skip to content

Commit

Permalink
feat: Expose formatting menu bar actions through slash command
Browse files Browse the repository at this point in the history
Signed-off-by: Julius Härtl <[email protected]>
  • Loading branch information
juliusknorr authored and max-nextcloud committed Aug 9, 2023
1 parent 2716cfd commit 9337c7e
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 19 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
],
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1",
"\\.(css)$": "identity-obj-proxy"
"^.+\\.(css|less|scss)$": "identity-obj-proxy"
},
"testPathIgnorePatterns": [
"<rootDir>/src/tests/fixtures/",
Expand Down
5 changes: 3 additions & 2 deletions src/components/Suggestion/LinkPicker/LinkPickerList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
:items="items"
@select="(item) => $emit('select', item)">
<template #default="{ item }">
<div class="link-picker__item">
<img :src="item.icon">
<div class="link-picker__item" :data-key="item.key">
<compoent :is="item.icon" v-if="typeof item.icon !== 'string'" />
<img v-else :src="item.icon">
<div>{{ item.label }}</div>
</div>
</template>
Expand Down
67 changes: 57 additions & 10 deletions src/components/Suggestion/LinkPicker/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,50 @@

import createSuggestions from '../suggestions.js'
import LinkPickerList from './LinkPickerList.vue'

import { searchProvider, getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js'
import menuEntries from './../../Menu/entries.js'
import { getIsActive } from '../../Menu/utils.js'

const suggestGroupFormat = t('text', 'Formatting')
const suggestGroupPicker = t('text', 'Smart picker')

const filterOut = (e) => {
return ['undo', 'redo', 'outline', 'emoji-picker'].indexOf(e.key) > -1
}

const important = ['task-list', 'table']

const sortImportantFirst = (list) => {
return [
...list.filter(e => important.indexOf(e.key) > -1),
...list.filter(e => important.indexOf(e.key) === -1),
]
}

const formattingSuggestions = (query) => {
return sortImportantFirst(
[
...menuEntries.find(e => e.key === 'headings').children,
...menuEntries.filter(e => e.action && !filterOut(e)),
...menuEntries.find(e => e.key === 'callouts').children,
{
...menuEntries.find(e => e.key === 'emoji-picker'),
action: (command) => command.insertContent(':'),
},
].filter(e => e?.label?.toLowerCase?.()?.includes(query.toLowerCase()))
.map(e => ({ ...e, suggestGroup: suggestGroupFormat })),
)
}

export default () => createSuggestions({
listComponent: LinkPickerList,
command: ({ editor, range, props }) => {
if (props.action) {
const commandChain = editor.chain().deleteRange(range)
props.action(commandChain)
commandChain.run()
return
}
getLinkWithPicker(props.providerId, true)
.then(link => {
editor
Expand All @@ -39,14 +77,23 @@ export default () => createSuggestions({
console.error('Smart picker promise rejected', error)
})
},
items: ({ query }) => {
return searchProvider(query)
.map(p => {
return {
label: p.title,
icon: p.icon_url,
providerId: p.id,
}
})
items: ({ editor, query }) => {
return [
...formattingSuggestions(query)
.filter(({ action, isActive }) => {
const canRunState = action(editor?.can())
const isActiveState = isActive && getIsActive({ isActive }, editor)
return canRunState && !isActiveState
}),
...searchProvider(query)
.map(p => {
return {
suggestGroup: suggestGroupPicker,
label: p.title,
icon: p.icon_url,
providerId: p.id,
}
}),
]
},
})
52 changes: 46 additions & 6 deletions src/components/Suggestion/SuggestionListWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@
<template>
<div class="suggestion-list">
<template v-if="hasResults">
<div v-for="(item, index) in items"
:key="index"
class="suggestion-list__item"
:class="{ 'is-selected': index === selectedIndex }"
@click="selectItem(index)">
<slot :item="item" :active="index === selectedIndex" />
<div v-for="(groupItems, key, groupIndex) in itemGroups" :key="key">
<div class="suggestion-list__group">
{{ key }}
</div>
<div v-for="(item, index) in groupItems"
:key="combineIndex(groupIndex, index)"
class="suggestion-list__item"
:class="{ 'is-selected': combineIndex(groupIndex, index) === selectedIndex }"
@click="selectItem(combineIndex(groupIndex, index))">
<slot :item="item" :active="combineIndex(groupIndex, index) === selectedIndex" />
</div>
</div>
</template>
<div v-else class="suggestion-list__item is-empty">
Expand Down Expand Up @@ -68,6 +73,26 @@ export default {
return this.selectedIndex * this.itemHeight >= this.$el.scrollTop
&& (this.selectedIndex + 1) * this.itemHeight <= this.$el.scrollTop + this.$el.clientHeight
},
itemGroups() {
const groups = {}
this.items.forEach((item) => {
if (!groups[item.suggestGroup]) {
groups[item.suggestGroup] = []
}
groups[item.suggestGroup].push(item)
})
return groups
},
combineIndex() {
return (groupIndex, index) => {
const previousItemCount = Object.values(this.itemGroups)
.slice(0, groupIndex)
.reduce((sum, items) => {
return sum + items.length
}, 0)
return previousItemCount + index
}
},
},
watch: {
items() {
Expand Down Expand Up @@ -128,11 +153,26 @@ export default {
min-width: 200px;
max-width: 400px;
width: 80vw;
padding: 4px;
// Show maximum 5 entries and a half to show scroll
max-height: 35.5px * 5 + 18px;
margin: 5px 0;
&__group {
font-weight: bold;
color: var(--color-primary-element);
font-size: var(--default-font-size);
line-height: 44px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: .7;
box-shadow: none !important;
flex-shrink: 0;
padding-left: 8px;
}
&__item {
border-radius: 8px;
padding: 4px 8px;
Expand Down

0 comments on commit 9337c7e

Please sign in to comment.