Skip to content

Commit

Permalink
feat: keyboard shortcuts (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker authored Jul 29, 2022
1 parent ed10520 commit a58f268
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 49 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
45 changes: 10 additions & 35 deletions src/components/toolbars/toolbar/actions/text/text.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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() {
Expand Down
53 changes: 44 additions & 9 deletions src/components/toolbars/toolbar/toolbar/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -553,20 +586,22 @@ export class Toolbar implements ComponentInterface {
private switchToolbarActions = (actions: ToolbarActions) => (this.toolbarActions = actions);

private onExecCommand = ($event: CustomEvent<ExecCommandAction>) => {
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);
}

Expand All @@ -575,7 +610,7 @@ export class Toolbar implements ComponentInterface {
}

this.styleDidChange.emit(toHTMLElement(this.selectionParagraph));
};
}

private onAttributesChangesInitStyle() {
const anchorNode: HTMLElement | null = getAnchorElement(this.selection);
Expand Down
9 changes: 7 additions & 2 deletions src/events/tab.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,9 @@ <h2 paragraph_id><span style="color: rgb(255, 101, 169);">Stylo</span></h2>
<h2 paragraph_id>Features</h2>
<ul paragraph_id><li>Interactive design 🎯</li><li>Customizable 💪</li><li>Framework agnostic 😎</li><li>Lightweight (30kb gzipped, 0 deps) 🪶</li><li>Future Proof 🚀</li><li>Open Source (MIT license) ⭐️</li></ul>
<hr paragraph_id/>
<div paragraph_id>Pro tips: trigger <span style="font-weight: bold;">bold</span> mode with "**", <span style="font-style: italic">italic</span> mode with whitespace and "_", and <mark>mark</mark> mode with "`". 👾</div>
<div paragraph_id>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.</div>
<hr paragraph_id/>
<div paragraph_id>Pro tips: trigger <span style="font-weight: bold;">bold</span> mode with "**", <span style="font-style: italic">italic</span> mode with whitespace and "_", and <mark>mark</mark> mode with "`" while you are typing. 👾</div>
<div paragraph_id>&ZeroWidthSpace;</div>
<div paragraph_id>Cheers<br/>David</div>
<div paragraph_id>&ZeroWidthSpace;</div>
Expand Down
39 changes: 39 additions & 0 deletions src/utils/execcomand-text.utils.ts
Original file line number Diff line number Diff line change
@@ -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'
}
};

0 comments on commit a58f268

Please sign in to comment.