diff --git a/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsSetIcon.ts b/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsSetIcon.ts index 9eae09f0433..69d79565c87 100644 --- a/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsSetIcon.ts +++ b/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsSetIcon.ts @@ -37,7 +37,7 @@ export const useSpaceActionsSetIcon = () => { dispatchModal({ elementClass: 'w-auto', title: $gettext('Set icon for ยป%{space}ยซ', { space: resources[0].name }), - hideConfirmButton: true, + hideActions: true, customComponent: markRaw(EmojiPickerModal), focusTrapInitial: false, onConfirm: (emoji: string) => setIconSpace(resources[0], emoji) diff --git a/packages/web-pkg/src/components/Modals/EmojiPickerModal.vue b/packages/web-pkg/src/components/Modals/EmojiPickerModal.vue index 083c8eb1677..a5a8cf8d9b0 100644 --- a/packages/web-pkg/src/components/Modals/EmojiPickerModal.vue +++ b/packages/web-pkg/src/components/Modals/EmojiPickerModal.vue @@ -1,5 +1,7 @@ + diff --git a/packages/web-pkg/src/editor/composables/strategies/html.ts b/packages/web-pkg/src/editor/composables/strategies/html.ts index 2f6a315b983..5a1b431e4a5 100644 --- a/packages/web-pkg/src/editor/composables/strategies/html.ts +++ b/packages/web-pkg/src/editor/composables/strategies/html.ts @@ -202,8 +202,8 @@ export const useStrategyHtml = (editorState: TextEditorState): ContentTypeStrate imageUpload(), imageCloud(), createTable(), - menuEmoji(), - horizontalRule() + horizontalRule(), + menuEmoji() ] }, { diff --git a/packages/web-pkg/src/editor/composables/strategies/markdown.ts b/packages/web-pkg/src/editor/composables/strategies/markdown.ts index 99237975647..1d024d0ae6c 100644 --- a/packages/web-pkg/src/editor/composables/strategies/markdown.ts +++ b/packages/web-pkg/src/editor/composables/strategies/markdown.ts @@ -246,8 +246,8 @@ export const useStrategyMarkdown = (editorState: TextEditorState): ContentTypeSt addColumnAfter(), deleteColumn(), deleteTable(), - menuEmoji(), horizontalRule(), + menuEmoji(), frontmatter() ] }, diff --git a/packages/web-pkg/src/editor/composables/strategies/tiptapJson.ts b/packages/web-pkg/src/editor/composables/strategies/tiptapJson.ts index 710c8873ecd..af82a320ebf 100644 --- a/packages/web-pkg/src/editor/composables/strategies/tiptapJson.ts +++ b/packages/web-pkg/src/editor/composables/strategies/tiptapJson.ts @@ -192,8 +192,8 @@ export const useStrategyTiptapJson = (editorState: TextEditorState): ContentType imageUpload(), imageCloud(), createTable(), - menuEmoji(), - horizontalRule() + horizontalRule(), + menuEmoji() ] }, { diff --git a/packages/web-pkg/src/editor/composables/useEditorActions.ts b/packages/web-pkg/src/editor/composables/useEditorActions.ts index 89aebdc499e..1a18b600638 100644 --- a/packages/web-pkg/src/editor/composables/useEditorActions.ts +++ b/packages/web-pkg/src/editor/composables/useEditorActions.ts @@ -8,6 +8,7 @@ import { OcEmojiPicker } from '@opencloud-eu/design-system/components' import { useModals, useThemeStore } from '../../composables' import { useClientService, useGetMatchingSpace, useFolderLink } from '../../composables' import FilePickerModal from '../../components/Modals/FilePickerModal.vue' +import EmojiPickerModal from '../../components/Modals/EmojiPickerModal.vue' import { arrayBufferToDataUrl, withoutExtension } from '../../helpers' import { TextEditorState } from '../types' import { requestLinkPanel, printEditorContent } from '../helpers' @@ -741,7 +742,18 @@ export function useEditorActions(state: TextEditorState) { icon: 'emoji-sticker', iconFillType: 'line', keywords: ['emoji', 'smiley', 'emoticon'], - showInSlashCommands: false, + showInSlashCommands: true, + slashCommandAction: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).run() + dispatchModal({ + title: $gettext('Insert emoji'), + customComponent: markRaw(EmojiPickerModal), + hideActions: true, + onConfirm: (emoji: string) => { + editor.chain().focus().insertContent(emoji).run() + } + }) + }, menuCloseOnClick: false, menuComponent: markRaw(OcEmojiPicker), menuComponentAttrs: (editor, closeMenu) => ({ diff --git a/packages/web-pkg/tests/unit/editor/composables/useEditorActions.spec.ts b/packages/web-pkg/tests/unit/editor/composables/useEditorActions.spec.ts index 373603f4e6e..14fe3d14028 100644 --- a/packages/web-pkg/tests/unit/editor/composables/useEditorActions.spec.ts +++ b/packages/web-pkg/tests/unit/editor/composables/useEditorActions.spec.ts @@ -1058,11 +1058,12 @@ describe('useEditorActions', () => { }) describe('menuEmoji', () => { - it('uses menu component mode and is hidden from slash commands', () => { + it('uses menu component mode and is available in slash commands', () => { const action = actions.menuEmoji() expect(action.id).toBe('menu-emoji') expect(action.menuComponent).toBeTruthy() - expect(action.showInSlashCommands).toBe(false) + expect(action.showInSlashCommands).toBe(true) + expect(action.slashCommandAction).toBeTypeOf('function') }) it('menuComponentAttrs inserts selected emoji into editor', () => { @@ -1077,6 +1078,24 @@ describe('useEditorActions', () => { expect(editor._chain.run).toHaveBeenCalled() expect(closeMenu).toHaveBeenCalled() }) + + it('slashCommandAction removes slash token and opens emoji modal', () => { + const editor = createMockEditor() + const action = actions.menuEmoji() + + action.slashCommandAction!({ editor, range: mockRange }) + + expect(editor._chain.deleteRange).toHaveBeenCalledWith(mockRange) + + const store = useModals() + const modal = store.modals[0] + expect(modal.title).toBe('Insert emoji') + expect(modal.hideActions).toBe(true) + + modal.onConfirm?.('๐Ÿ˜€') + expect(editor._chain.insertContent).toHaveBeenCalledWith('๐Ÿ˜€') + expect(editor._chain.run).toHaveBeenCalled() + }) }) describe('action metadata', () => { diff --git a/packages/web-pkg/tests/unit/editor/composables/useTextEditor.spec.ts b/packages/web-pkg/tests/unit/editor/composables/useTextEditor.spec.ts index f3b89163bec..dde67189a77 100644 --- a/packages/web-pkg/tests/unit/editor/composables/useTextEditor.spec.ts +++ b/packages/web-pkg/tests/unit/editor/composables/useTextEditor.spec.ts @@ -92,14 +92,14 @@ describe('useTextEditor', () => { expect(pluginNames).toContain('slashCommands') }) - it('does not register the extension for plain-text when all actions are hidden in slash commands', () => { + it('registers the extension for plain-text when a slash action is available', () => { const { result } = createEditor({ contentType: 'plain-text', modelValue: toRef('hi'), slashCommands: true }) const pluginNames = result.editor.value?.extensionManager.extensions.map((e) => e.name) ?? [] - expect(pluginNames).not.toContain('slashCommands') + expect(pluginNames).toContain('slashCommands') }) }) diff --git a/packages/web-pkg/tests/unit/editor/strategies/html.spec.ts b/packages/web-pkg/tests/unit/editor/strategies/html.spec.ts index 1e12c97159b..b0576f58b8e 100644 --- a/packages/web-pkg/tests/unit/editor/strategies/html.spec.ts +++ b/packages/web-pkg/tests/unit/editor/strategies/html.spec.ts @@ -86,9 +86,10 @@ describe('useStrategyHtml', () => { const tableToolsGroup = strategy .editorActionGroups() .find((group) => group.id === 'table-tools') + const insertIds = insertGroup?.actions.map((action) => action.id) ?? [] - expect(insertGroup?.actions.map((action) => action.id)).toContain('table') - expect(insertGroup?.actions.map((action) => action.id)).not.toContain('add-row-before') + expect(insertIds).toContain('table') + expect(insertIds).not.toContain('add-row-before') expect(tableToolsGroup?.actions.map((action) => action.id)).toEqual([ 'toggle-header-row', 'add-row-before', diff --git a/packages/web-pkg/tests/unit/editor/strategies/markdown.spec.ts b/packages/web-pkg/tests/unit/editor/strategies/markdown.spec.ts index 05c25dadf72..a4dbb79cca7 100644 --- a/packages/web-pkg/tests/unit/editor/strategies/markdown.spec.ts +++ b/packages/web-pkg/tests/unit/editor/strategies/markdown.spec.ts @@ -144,14 +144,14 @@ describe('useStrategyMarkdown', () => { expect(enabled.filter((id) => !allowed.includes(id) && !id.startsWith('zoom-'))).toEqual([]) }) - it('offers no slash entries in the frontmatter block', () => { + it('offers the emoji slash entry in the frontmatter block', () => { const editor = editorIn('frontmatter') const slashItems = allActions() .filter((action) => action.showInSlashCommands !== false) .filter((action) => action.isEnabled?.(editor) ?? true) .map(({ id }) => id) - expect(slashItems).toEqual([]) + expect(slashItems).toEqual(['menu-emoji']) }) it('leaves everything enabled outside the block', () => { diff --git a/packages/web-pkg/tests/unit/editor/strategies/tiptapJson.spec.ts b/packages/web-pkg/tests/unit/editor/strategies/tiptapJson.spec.ts index a6b8c8401cb..3c13d019917 100644 --- a/packages/web-pkg/tests/unit/editor/strategies/tiptapJson.spec.ts +++ b/packages/web-pkg/tests/unit/editor/strategies/tiptapJson.spec.ts @@ -57,10 +57,11 @@ describe('useStrategyTiptapJson', () => { const tableToolsGroup = strategy .editorActionGroups() .find((group) => group.id === 'table-tools') + const insertIds = insertGroup?.actions.map((action) => action.id) ?? [] - expect(insertGroup?.actions.map((action) => action.id)).toContain('image-cloud') - expect(insertGroup?.actions.map((action) => action.id)).toContain('table') - expect(insertGroup?.actions.map((action) => action.id)).not.toContain('add-row-before') + expect(insertIds).toContain('image-cloud') + expect(insertIds).toContain('table') + expect(insertIds).not.toContain('add-row-before') expect(tableToolsGroup?.actions.map((action) => action.id)).toEqual([ 'toggle-header-row', 'add-row-before',