diff --git a/models/love/src/index.ts b/models/love/src/index.ts index 729fbf08d7..3aaafeaa03 100644 --- a/models/love/src/index.ts +++ b/models/love/src/index.ts @@ -399,7 +399,7 @@ export function createModel (builder: Builder): void { icon: love.icon.Mic, keyBinding: ['Meta + keyD'], category: love.category.Office, - allowedForEditableContent: true, + allowedForEditableContent: 'always', input: 'none', target: core.class.Doc, context: { @@ -415,7 +415,7 @@ export function createModel (builder: Builder): void { action: love.actionImpl.ToggleVideo, label: love.string.Camera, icon: love.icon.Cam, - allowedForEditableContent: true, + allowedForEditableContent: 'always', keyBinding: ['Meta + keyE'], category: love.category.Office, input: 'none', diff --git a/models/view/src/index.ts b/models/view/src/index.ts index 968100c190..e83609c617 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -866,7 +866,7 @@ export function createModel (builder: Builder): void { category: view.category.GeneralNavigation, input: 'none', target: core.class.Doc, - allowedForEditableContent: true, + allowedForEditableContent: 'noSelection', context: { mode: ['workbench', 'browser', 'panel', 'editor', 'input'] } diff --git a/plugins/text-editor-resources/src/components/extension/link.ts b/plugins/text-editor-resources/src/components/extension/link.ts new file mode 100644 index 0000000000..4f67a800e8 --- /dev/null +++ b/plugins/text-editor-resources/src/components/extension/link.ts @@ -0,0 +1,43 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Extension } from '@tiptap/core' +import { showPopup } from '@hcengineering/ui' +import LinkPopup from '../LinkPopup.svelte' + +export const LinkShortcutsExtension = Extension.create({ + name: 'defaultShortcuts', + + addKeyboardShortcuts () { + return { + 'Mod-k': () => { + const { from, to } = this.editor.state.selection + if (from === to) return false + + const link = this.editor.getAttributes('link').href + + showPopup(LinkPopup, { link }, undefined, undefined, (newLink) => { + if (newLink === '') { + this.editor.chain().focus().extendMarkRange('link').unsetLink().run() + } else { + this.editor.chain().focus().extendMarkRange('link').setLink({ href: newLink }).run() + } + }) + + return true + } + } + } +}) diff --git a/plugins/text-editor-resources/src/kits/editor-kit.ts b/plugins/text-editor-resources/src/kits/editor-kit.ts index 3b9adc535a..f59213e70f 100644 --- a/plugins/text-editor-resources/src/kits/editor-kit.ts +++ b/plugins/text-editor-resources/src/kits/editor-kit.ts @@ -46,6 +46,7 @@ import { MermaidExtension, type MermaidOptions, mermaidOptions } from '../compon import { DrawingBoardExtension, type DrawingBoardOptions } from '../components/extension/drawingBoard' import { type IndendOptions, IndentExtension, indentExtensionOptions } from '../components/extension/indent' import TextAlign, { type TextAlignOptions } from '@tiptap/extension-text-align' +import { LinkShortcutsExtension } from '../components/extension/link' export interface EditorKitOptions extends DefaultKitOptions { history?: false @@ -315,6 +316,8 @@ async function buildEditorKit (): Promise> { ]) } + staticKitExtensions.push([950, LinkShortcutsExtension.configure({})]) + if (mode !== 'compact' && this.options.note !== false) { staticKitExtensions.push([1000, NoteExtension.configure(this.options.note ?? {})]) } diff --git a/plugins/view-resources/src/components/ActionHandler.svelte b/plugins/view-resources/src/components/ActionHandler.svelte index 7a93ea8747..859f018a4e 100644 --- a/plugins/view-resources/src/components/ActionHandler.svelte +++ b/plugins/view-resources/src/components/ActionHandler.svelte @@ -140,6 +140,9 @@ let elm = evt.target as HTMLElement let isContentEditable = false + const selection = window.getSelection() + const haveRangeSelection = !(selection?.isCollapsed ?? true) + while (true) { if (elm.isContentEditable) { isContentEditable = true @@ -195,9 +198,20 @@ let postpone = false let nonSequenceAction: Action | undefined + const checkIsAllowedContentMode = (mode?: 'always' | 'noSelection'): boolean => { + if (!isContentEditable) return true + switch (mode) { + case 'always': + return true + case 'noSelection': + return !haveRangeSelection + } + return false + } + for (const a of currentActions) { if (a.keyBinding === undefined || a.keyBinding.length < 1) continue - if (isContentEditable && a.allowedForEditableContent !== true) continue + if (!checkIsAllowedContentMode(a.allowedForEditableContent)) continue const t = lastKey if (t !== undefined && a.keyBinding.some((it) => matchKeySequence(evt, it, t))) { evt.preventDefault() diff --git a/plugins/view/src/types.ts b/plugins/view/src/types.ts index b5064ab2a6..b77614f365 100644 --- a/plugins/view/src/types.ts +++ b/plugins/view/src/types.ts @@ -541,7 +541,7 @@ export interface Action> extends Do // Available only for workspace owners secured?: boolean - allowedForEditableContent?: boolean + allowedForEditableContent?: 'always' | 'noSelection' analyticsEvent?: string }