From a58f268c2407d99f5f2947304bccbdeb3850d666 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 29 Jul 2022 17:04:53 +0200 Subject: [PATCH] feat: keyboard shortcuts (#107) --- CHANGELOG.md | 6 +++ package-lock.json | 2 +- package.json | 2 +- .../toolbars/toolbar/actions/text/text.tsx | 45 ++++------------ .../toolbars/toolbar/toolbar/toolbar.tsx | 53 +++++++++++++++---- src/events/tab.events.ts | 9 +++- src/index.html | 4 +- src/utils/execcomand-text.utils.ts | 39 ++++++++++++++ 8 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 src/utils/execcomand-text.utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index eeec1ee..bd29d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.0.36 (2022-07-29) + +## Features + +- keyboard shortcuts: control or command-{B, I, U, K} for bold, italic, underline, insert hyperlink + # 0.0.35-1 (2022-07-29) ### Fix diff --git a/package-lock.json b/package-lock.json index 78f796c..b333dd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@papyrs/stylo", - "version": "0.0.35-1", + "version": "0.0.36", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7f8cbb5..4bd8cdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@papyrs/stylo", - "version": "0.0.35-1", + "version": "0.0.36", "description": "Another kind of rich text editor", "author": "David Dal Busco", "license": "MIT", diff --git a/src/components/toolbars/toolbar/actions/text/text.tsx b/src/components/toolbars/toolbar/actions/text/text.tsx index 6e8249e..86884fa 100644 --- a/src/components/toolbars/toolbar/actions/text/text.tsx +++ b/src/components/toolbars/toolbar/actions/text/text.tsx @@ -1,6 +1,12 @@ import {Component, Event, EventEmitter, h, Host, Prop} from '@stencil/core'; import i18n from '../../../../../stores/i18n.store'; import {ExecCommandAction} from '../../../../../types/execcommand'; +import { + actionBold, + actionItalic, + actionStrikeThrough, + actionUnderline +} from '../../../../../utils/execcomand-text.utils'; @Component({ tag: 'stylo-toolbar-text', @@ -29,56 +35,25 @@ export class Text { private styleBold($event: UIEvent) { $event.stopPropagation(); - this.execCommand.emit({ - cmd: 'style', - detail: { - style: 'font-weight', - value: 'bold', - initial: (element: HTMLElement | null) => element && element.style['font-weight'] === 'bold' - } - }); + this.execCommand.emit(actionBold); } private styleItalic($event: UIEvent) { $event.stopPropagation(); - this.execCommand.emit({ - cmd: 'style', - detail: { - style: 'font-style', - value: 'italic', - initial: (element: HTMLElement | null) => - element && element.style['font-style'] === 'italic' - } - }); + this.execCommand.emit(actionItalic); } private styleUnderline($event: UIEvent) { $event.stopPropagation(); - this.execCommand.emit({ - cmd: 'style', - detail: { - style: 'text-decoration', - value: 'underline', - initial: (element: HTMLElement | null) => - element && element.style['text-decoration'] === 'underline' - } - }); + this.execCommand.emit(actionUnderline); } private styleStrikeThrough($event: UIEvent) { $event.stopPropagation(); - this.execCommand.emit({ - cmd: 'style', - detail: { - style: 'text-decoration', - value: 'line-through', - initial: (element: HTMLElement | null) => - element && element.style['text-decoration'] === 'line-through' - } - }); + this.execCommand.emit(actionStrikeThrough); } render() { diff --git a/src/components/toolbars/toolbar/toolbar/toolbar.tsx b/src/components/toolbars/toolbar/toolbar/toolbar.tsx index 1099563..50f9651 100644 --- a/src/components/toolbars/toolbar/toolbar/toolbar.tsx +++ b/src/components/toolbars/toolbar/toolbar/toolbar.tsx @@ -28,7 +28,8 @@ import { ToolbarFontSize, ToolbarList } from '../../../../types/toolbar'; -import {execCommand} from '../../../../utils/execcommand.utils'; +import {actionBold, actionItalic, actionUnderline} from '../../../../utils/execcomand-text.utils'; +import {execCommand as execCommandCustom} from '../../../../utils/execcommand.utils'; import {execCommandNative} from '../../../../utils/execcommnad-native.utils'; import {removeLink} from '../../../../utils/link.utils'; import {isMobile} from '../../../../utils/mobile.utils'; @@ -200,8 +201,40 @@ export class Toolbar implements ComponentInterface { this.reset(false); return; } + + this.styleKeyboardShortcuts($event); }; + private styleKeyboardShortcuts($event: KeyboardEvent) { + const {metaKey, ctrlKey, key} = $event; + + if (!metaKey && !ctrlKey) { + return; + } + + if (!['b', 'i', 'u', 'k'].includes(key)) { + return; + } + + $event.preventDefault(); + $event.stopPropagation(); + + switch (key) { + case 'b': + this.execCommand(actionBold); + break; + case 'i': + this.execCommand(actionItalic); + break; + case 'u': + this.execCommand(actionUnderline); + break; + case 'k': + this.openLink(); + break; + } + } + @Listen('contextmenu', {target: 'document', passive: true}) onContextMenu() { this.reset(false); @@ -246,7 +279,7 @@ export class Toolbar implements ComponentInterface { const listenerElement: HTMLElement | Document = this.containerRef || document; listenerElement?.addEventListener('mousedown', this.startSelection, {passive: true}); listenerElement?.addEventListener('touchstart', this.startSelection, {passive: true}); - listenerElement?.addEventListener('keydown', this.onKeyDown, {passive: true}); + listenerElement?.addEventListener('keydown', this.onKeyDown); } private removeListener() { @@ -553,20 +586,22 @@ export class Toolbar implements ComponentInterface { private switchToolbarActions = (actions: ToolbarActions) => (this.toolbarActions = actions); private onExecCommand = ($event: CustomEvent) => { - if (!$event || !$event.detail) { - return; - } + this.execCommand($event.detail); + }; + private execCommand(action: ExecCommandAction) { // onSelectionChange is triggered if DOM changes, we still need to detect attributes changes to refresh style this.onAttributesChangesInitStyle(); if (configStore.state.toolbar.command === 'native') { - execCommandNative($event.detail); + execCommandNative(action); } else { - execCommand(this.selection, $event.detail, this.containerRef); + execCommandCustom(this.selection, action, this.containerRef); } - if ($event.detail.cmd === 'list' || isIOS()) { + const {cmd} = action; + + if (cmd === 'list' || isIOS()) { this.reset(true); } @@ -575,7 +610,7 @@ export class Toolbar implements ComponentInterface { } this.styleDidChange.emit(toHTMLElement(this.selectionParagraph)); - }; + } private onAttributesChangesInitStyle() { const anchorNode: HTMLElement | null = getAnchorElement(this.selection); diff --git a/src/events/tab.events.ts b/src/events/tab.events.ts index 1bcc26f..f0dbdeb 100644 --- a/src/events/tab.events.ts +++ b/src/events/tab.events.ts @@ -96,7 +96,10 @@ export class TabEvents { // If list contains a single child that is just text then browser returns the list as focus node const focusNodeIsList: boolean = node !== undefined && isNodeList({node: node}); - const paragraphListNodeName: 'ul' | 'ol' | 'dl' = paragraph.nodeName.toLowerCase() as 'ul' | 'ol' | 'dl'; + const paragraphListNodeName: 'ul' | 'ol' | 'dl' = paragraph.nodeName.toLowerCase() as + | 'ul' + | 'ol' + | 'dl'; const li: Node | undefined = focusNodeIsList ? node.firstChild @@ -193,7 +196,9 @@ export class TabEvents { return; } - const focusNode: Node | null = isNodeList({node: addedFirstNode}) ? addedFirstNode.firstChild : addedFirstNode; + const focusNode: Node | null = isNodeList({node: addedFirstNode}) + ? addedFirstNode.firstChild + : addedFirstNode; // Move cursor to new li. If empty we move to start because maybe it contains a br a last child. if (addedFirstNode.textContent.length === 0) { diff --git a/src/index.html b/src/index.html index ec19f12..9a72de9 100644 --- a/src/index.html +++ b/src/index.html @@ -513,7 +513,9 @@

Stylo

Features


-
Pro tips: trigger bold mode with "**", italic mode with whitespace and "_", and mark mode with "`". ðŸ‘ū
+
Select text and use the floating toolbar to apply style or transform to url. It supports keyboard shortcuts as well: Control or Command-{b, i, u, k} for bold, italic, underline & insert hyperlink.
+
+
Pro tips: trigger bold mode with "**", italic mode with whitespace and "_", and mark mode with "`" while you are typing. ðŸ‘ū
Cheers
David
diff --git a/src/utils/execcomand-text.utils.ts b/src/utils/execcomand-text.utils.ts new file mode 100644 index 0000000..d24ac79 --- /dev/null +++ b/src/utils/execcomand-text.utils.ts @@ -0,0 +1,39 @@ +import {ExecCommandAction} from '../types/execcommand'; + +export const actionBold: ExecCommandAction = { + cmd: 'style', + detail: { + style: 'font-weight', + value: 'bold', + initial: (element: HTMLElement | null) => element && element.style['font-weight'] === 'bold' + } +}; + +export const actionItalic: ExecCommandAction = { + cmd: 'style', + detail: { + style: 'font-style', + value: 'italic', + initial: (element: HTMLElement | null) => element && element.style['font-style'] === 'italic' + } +}; + +export const actionUnderline: ExecCommandAction = { + cmd: 'style', + detail: { + style: 'text-decoration', + value: 'underline', + initial: (element: HTMLElement | null) => + element && element.style['text-decoration'] === 'underline' + } +}; + +export const actionStrikeThrough: ExecCommandAction = { + cmd: 'style', + detail: { + style: 'text-decoration', + value: 'line-through', + initial: (element: HTMLElement | null) => + element && element.style['text-decoration'] === 'line-through' + } +};