json-joy
Version:
Collection of libraries for building collaborative editing apps.
1,108 lines (1,107 loc) • 72.3 kB
JavaScript
import * as React from 'react';
import { Sidetip } from 'nice-ui/lib/1-inline/Sidetip';
import { Iconista } from 'nice-ui/lib/icons/Iconista';
import { ValueSyncStore } from '../../../../util/events/sync-store';
import { secondBrain } from './menus';
import { Code } from 'nice-ui/lib/1-inline/Code';
import { FontStyleButton } from 'nice-ui/lib/2-inline-block/FontStyleButton';
import { CommonSliceType } from '../../../../json-crdt-extensions';
import { BehaviorSubject } from 'rxjs';
import { compare } from '../../../../json-crdt-patch';
import { NewFormatting } from './formattings';
import * as behavior from '../formatting/tags';
export class ToolbarState {
surface;
opts;
txt;
lastEvent = void 0;
lastEventTs = 0;
showInlineToolbar = new ValueSyncStore([false, 0]);
/**
* New slice configuration. This is used for new slices which are not yet
* applied to the text as they need to be configured first.
*/
newSlice = new ValueSyncStore(void 0);
/**
* The ID of the active (where the main cursor or focus is placed) leaf block.
*/
activeLeafBlockId$ = new BehaviorSubject(null);
constructor(surface, opts) {
this.surface = surface;
this.opts = opts;
this.txt = this.surface.dom.txt;
}
_setActiveLeafBlockId = () => {
const { activeLeafBlockId$, txt } = this;
const { overlay, editor } = txt;
const value = activeLeafBlockId$.getValue();
const cardinality = editor.cursorCard();
if (cardinality !== 1 || (cardinality === 1 && !editor.mainCursor()?.isCollapsed())) {
if (value)
activeLeafBlockId$.next(null);
return;
}
const focus = editor.mainCursor()?.focus();
const marker = focus ? overlay.getOrNextLowerMarker(focus) : void 0;
const markerId = marker?.marker.start.id ?? txt.str.id;
const doSet = !value || compare(value, markerId) !== 0;
if (doSet)
activeLeafBlockId$.next(markerId);
};
setLastEv(lastEvent) {
this.lastEvent = lastEvent;
this.lastEventTs = Date.now();
}
// private doShowInlineToolbar(): boolean {
// const {surface, lastEvent} = this;
// if (surface.dom!.cursor.mouseDown.value) return false;
// if (!lastEvent) return false;
// const lastEventIsCursorEvent = lastEvent?.type === 'cursor';
// if (!lastEventIsCursorEvent) return false;
// if (!surface.peritext.editor.cursorCount()) return false;
// return true;
// }
startSliceConfig(tag, menu) {
const editor = this.txt.editor;
const behavior = editor.getRegistry().get(tag);
const range = editor.mainCursor()?.range();
if (!range)
return;
const newSlice = this.newSlice;
if (!behavior) {
newSlice.next(void 0);
return;
}
const formatting = new NewFormatting(behavior, range, this);
newSlice.next(formatting);
return formatting;
}
// public registerSlice(tag: TypeTag, data: SliceRegistryEntryData): ToolBarSliceRegistryEntry {
// const registry = this.txt.editor.getRegistry();
// const entry = registry.get(tag);
// }
/** ------------------------------------------- {@link UiLifeCycles} */
start() {
const { surface, showInlineToolbar, newSlice: newSliceConfig } = this;
const { dom, events } = surface;
const { et } = events;
const mouseDown = dom.cursor.mouseDown;
const el = dom.el;
const registry = this.txt.editor.getRegistry();
Object.assign(registry.get(-17 /* SliceTypeCon.a */)?.data() || {}, behavior.a);
// registry.add({});
Object.assign(registry.get(-29 /* SliceTypeCon.col */)?.data() || {}, behavior.col);
const changeUnsubscribe = et.subscribe('change', (ev) => {
const lastEvent = ev.detail.ev;
this.setLastEv(lastEvent);
this._setActiveLeafBlockId();
});
const unsubscribeMouseDown = mouseDown?.subscribe(() => {
// if (mouseDown.value) showInlineToolbar.next(false);
});
const mouseDownListener = (event) => {
// showInlineToolbar.next(false);
// if (showInlineToolbar.value[0])
// showInlineToolbar.next([false, Date.now()]);
};
const mouseUpListener = (event) => {
if (!showInlineToolbar.value[0])
showInlineToolbar.next([true, Date.now()]);
};
const onKeyDown = (event) => {
switch (event.key) {
case 'Escape': {
if (newSliceConfig.value) {
event.stopPropagation();
event.preventDefault;
newSliceConfig.next(void 0);
return;
}
break;
}
}
};
const onKeyDownDocument = (event) => {
switch (event.key) {
case 'k': {
if (event.metaKey) {
const editor = this.txt.editor;
if (editor.hasCursor() &&
!editor.mainCursor()?.isCollapsed() &&
(!newSliceConfig.value || newSliceConfig.value.behavior.tag !== -17 /* SliceTypeCon.a */)) {
event.stopPropagation();
event.preventDefault;
this.startSliceConfig(-17 /* SliceTypeCon.a */, this.linkMenuItem());
return;
}
}
break;
}
}
};
const onCursor = ({ detail }) => {
// Close config popup on non-focus cursor moves.
if (newSliceConfig.value) {
const isFocusMove = detail.move && detail.move.length === 1 && detail.move[0][0] === 'focus';
if (!isFocusMove) {
this.newSlice.next(void 0);
}
}
};
el.addEventListener('mousedown', mouseDownListener);
el.addEventListener('mouseup', mouseUpListener);
el.addEventListener('keydown', onKeyDown);
document.addEventListener('keydown', onKeyDownDocument);
et.addEventListener('cursor', onCursor);
const unsubscribeKeyHistory = dom.keys.history.subscribe(() => {
// Flip selection anchor and focus edges on quick [Meta, Meta] key sequence.
const keys = dom.keys.history.value;
const last = keys[keys.length - 1];
const beforeLast = keys[keys.length - 2];
if (last?.key === 'Meta' && beforeLast?.key === 'Meta')
if (last.ts - beforeLast.ts < 500)
et.cursor({ flip: true });
});
return () => {
changeUnsubscribe();
unsubscribeMouseDown?.();
el.removeEventListener('mousedown', mouseDownListener);
el.removeEventListener('mouseup', mouseUpListener);
el.removeEventListener('keydown', onKeyDown);
document.removeEventListener('keydown', onKeyDownDocument);
et.removeEventListener('cursor', onCursor);
unsubscribeKeyHistory();
};
}
// -------------------------------------------------------------------- Menus
getFormattingMenu = () => {
const et = this.surface.events.et;
return {
name: 'Formatting',
expandChild: 0,
children: [
{
name: 'Common',
expand: 8,
children: [
{
name: 'Bold',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "font-bold" }),
// icon: () => <Iconista width={16} height={16} set="lucide" icon="bold" />,
right: () => React.createElement(Sidetip, { small: true }, "\u2318 B"),
keys: ['⌘', 'b'],
onSelect: () => {
et.format('tog', CommonSliceType.b);
},
},
{
name: 'Italic',
// icon: () => <Iconista width={15} height={15} set="radix" icon="font-italic" />,
// icon: () => <Iconista width={16} height={16} set="lucide" icon="italic" />,
icon: () => React.createElement(Iconista, { width: 14, height: 14, set: "lucide", icon: "italic" }),
right: () => React.createElement(Sidetip, { small: true }, "\u2318 I"),
keys: ['⌘', 'i'],
onSelect: () => {
et.format('tog', CommonSliceType.i);
},
},
{
name: 'Underline',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "underline" }),
right: () => React.createElement(Sidetip, { small: true }, "\u2318 U"),
keys: ['⌘', 'u'],
onSelect: () => {
et.format('tog', CommonSliceType.u);
},
},
{
name: 'Strikethrough',
// icon: () => <Iconista width={15} height={15} set="radix" icon="strikethrough" />,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "strikethrough" }),
onSelect: () => {
et.format('tog', CommonSliceType.s);
},
},
{
name: 'Overline',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "overline" }),
onSelect: () => {
et.format('tog', CommonSliceType.overline);
},
},
{
name: 'Highlight',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "highlight" }),
onSelect: () => {
et.format('tog', CommonSliceType.mark);
},
},
{
name: 'Classified',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "lock-password" }),
onSelect: () => {
et.format('tog', CommonSliceType.spoiler);
},
},
],
},
{
name: 'Technical separator',
sep: true,
},
{
name: 'Technical',
expand: 8,
children: [
{
name: 'Code',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "code" }),
onSelect: () => {
et.format('tog', CommonSliceType.code);
},
},
{
name: 'Math',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "math-integral-x" }),
onSelect: () => {
et.format('tog', CommonSliceType.math);
},
},
{
name: 'Superscript',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "superscript" }),
onSelect: () => {
et.format('tog', CommonSliceType.sup);
},
},
{
name: 'Subscript',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "subscript" }),
onSelect: () => {
et.format('tog', CommonSliceType.sub);
},
},
{
name: 'Keyboard key',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "keyboard" }),
onSelect: () => {
et.format('tog', CommonSliceType.kbd);
},
},
{
name: 'Insertion',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "pencil-plus" }),
onSelect: () => {
et.format('tog', CommonSliceType.ins);
},
},
{
name: 'Deletion',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "pencil-minus" }),
onSelect: () => {
et.format('tog', CommonSliceType.del);
},
},
],
},
{
name: 'Artistic separator',
sep: true,
},
{
name: 'Artistic',
expand: 8,
children: [
this.colorMenuItem(),
{
name: 'Background',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "paint-bucket" }),
onSelect: () => { },
},
{
name: 'Border',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "border-left" }),
onSelect: () => { },
},
],
},
],
};
};
colorMenuItem = () => {
const colorItem = {
...behavior.col.menu,
onSelect: () => {
this.startSliceConfig(CommonSliceType.col, colorItem);
},
};
return colorItem;
};
linkMenuItem = () => {
const linkAction = {
...behavior.a.menu,
onSelect: () => {
this.startSliceConfig(CommonSliceType.a, linkAction);
},
};
return linkAction;
};
annotationsMenu = () => {
return {
name: 'Annotations',
expand: 2,
sepBefore: true,
children: [
this.linkMenuItem(),
// {
// name: 'Comment',
// icon: () => <Iconista width={16} height={16} set="lineicons" icon="comment-1-text" />,
// onSelect: () => {},
// },
// {
// name: 'Bookmark',
// icon: () => <Iconista width={16} height={16} set="lineicons" icon="flag-2" />,
// onSelect: () => {},
// },
// {
// name: 'Footnote',
// icon: () => <Iconista width={16} height={16} set="lucide" icon="footprints" />,
// onSelect: () => {},
// },
{
name: 'Aside',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "box-align-right" }),
onSelect: () => { },
},
],
};
};
modifyMenu = () => {
const et = this.surface.events.et;
return {
name: 'Modify',
expand: 3,
sepBefore: true,
children: [
{
name: 'Pick layer',
right: () => (React.createElement(Code, { size: -1, gray: true }, "9+")),
more: true,
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "layers" }),
onSelect: () => { },
},
{
name: 'Erase formatting',
danger: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "eraser" }),
onSelect: () => {
et.format({ action: 'erase' });
},
},
{
name: 'Delete all in range',
danger: true,
more: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "trash" }),
onSelect: () => {
et.format({ action: 'del' });
},
},
],
};
};
copyAsMenu = (action, ctx = {}) => {
const icon = action === 'copy'
? () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard-copy" })
: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "scissors" });
const et = this.surface.events.et;
const iconMarkdown = () => React.createElement(Iconista, { width: 16, height: 16, set: "simple", icon: "markdown", style: { opacity: 0.5 } });
const iconHtml = () => React.createElement(Iconista, { width: 14, height: 14, set: "simple", icon: "html5", style: { opacity: 0.5 } });
const iconJson = () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "json", style: { opacity: 0.5 } });
const markdownAction = {
name: 'Markdown',
text: action + ' markdown md',
icon,
right: iconMarkdown,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(markdownAction, action), action, format: 'md' });
},
};
const mdastAction = {
name: 'MDAST',
text: action + ' markdown md mdast',
icon,
right: iconMarkdown,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(mdastAction, action), action, format: 'mdast' });
},
};
const htmlAction = {
name: 'HTML',
text: action + ' html',
icon,
right: iconHtml,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(htmlAction, action), action, format: 'html' });
},
};
const hastAction = {
name: 'HAST',
text: action + ' html hast',
icon,
right: iconHtml,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(hastAction, action), action, format: 'hast' });
},
};
const jsonAction = {
name: 'Range view',
text: action + ' range view peritext',
icon,
right: iconJson,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(jsonAction, action), action, format: 'json' });
},
};
const jsonmlAction = {
name: 'Fragment ML',
text: action + ' peritext fragment ml node',
icon,
right: iconJson,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(jsonmlAction, action), action, format: 'jsonml' });
},
};
const fragmentAction = {
name: 'Fragment text',
text: action + 'peritext fragment debug',
icon,
right: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "text", style: { opacity: 0.5 } }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(fragmentAction, action), action, format: 'fragment' });
},
};
return {
name: action === 'copy' ? 'Copy as' : 'Cut as',
more: true,
icon,
children: [
markdownAction,
mdastAction,
{
name: 'MD sep',
sep: true,
},
htmlAction,
hastAction,
{
name: 'HTML sep',
sep: true,
},
jsonAction,
jsonmlAction,
fragmentAction,
],
};
};
pasteAsMenu = (ctx = {}) => {
const icon = () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard" });
const iconMarkdown = () => React.createElement(Iconista, { width: 16, height: 16, set: "simple", icon: "markdown", style: { opacity: 0.5 } });
const iconHtml = () => React.createElement(Iconista, { width: 14, height: 14, set: "simple", icon: "html5", style: { opacity: 0.5 } });
const iconJson = () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "json", style: { opacity: 0.5 } });
const et = this.surface.events.et;
const markdownAction = {
name: 'Markdown',
text: 'paste markdown md',
icon,
right: iconMarkdown,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(markdownAction, 'paste'), action: 'paste', format: 'md' });
},
};
const mdastAction = {
name: 'MDAST',
text: 'paste markdown md mdast',
icon,
right: iconMarkdown,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(mdastAction, 'paste'), action: 'paste', format: 'mdast' });
},
};
const htmlAction = {
name: 'HTML',
text: 'paste html',
icon,
right: iconHtml,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(htmlAction, 'paste'), action: 'paste', format: 'html' });
},
};
const hastAction = {
name: 'HAST',
text: 'paste html hast',
icon,
right: iconHtml,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(hastAction, 'paste'), action: 'paste', format: 'hast' });
},
};
const jsonAction = {
name: 'Range view',
text: 'paste range view peritext',
icon,
right: iconJson,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(jsonAction, 'paste'), action: 'paste', format: 'json' });
},
};
const jsonmlAction = {
name: 'Fragment ML',
text: 'paste peritext fragment ml node',
icon,
right: iconJson,
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(jsonmlAction, 'paste'), action: 'paste', format: 'jsonml' });
},
};
return {
name: 'Paste as',
more: true,
icon,
children: [
markdownAction,
mdastAction,
{
name: 'MD sep',
sep: true,
},
htmlAction,
hastAction,
{
name: 'HTML sep',
sep: true,
},
jsonAction,
jsonmlAction,
],
};
};
copyMenu = (ctx = {}) => {
const et = this.surface.events.et;
const copyAction = {
name: 'Copy',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard-copy" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(copyAction, 'copy'), action: 'copy' });
},
};
const copyTextOnlyAction = {
name: 'Copy text only',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard-copy" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(copyTextOnlyAction, 'copy'), action: 'copy', format: 'text' });
},
};
const children = [copyAction, copyTextOnlyAction];
if (!ctx.hideStyleActions) {
const copyStyleAction = {
name: 'Copy style',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard-copy" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(copyStyleAction, 'copy'), action: 'copy', format: 'style' });
},
};
children.push(copyStyleAction);
}
children.push(this.copyAsMenu('copy', ctx));
return {
id: 'copy-menu',
name: 'Copy',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard-copy" }),
expand: 5,
children,
};
};
cutMenu = (ctx = {}) => {
const et = this.surface.events.et;
const cutAction = {
name: 'Cut',
danger: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "scissors" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(cutAction, 'cut'), action: 'cut' });
},
};
const cutTextAction = {
name: 'Cut text only',
danger: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "scissors" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(cutTextAction, 'cut'), action: 'cut', format: 'text' });
},
};
return {
id: 'cut-menu',
name: 'Cut',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "scissors" }),
expand: 5,
children: [cutAction, cutTextAction, this.copyAsMenu('cut', ctx)],
};
};
pasteMenu = (ctx = {}) => {
const et = this.surface.events.et;
const pasteAction = {
name: 'Paste',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(pasteAction, 'paste'), action: 'paste' });
},
};
const pasteTextAction = {
name: 'Paste text',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(pasteTextAction, 'paste'), action: 'paste', format: 'text' });
},
};
const children = [pasteAction, pasteTextAction];
if (!ctx.hideStyleActions) {
const pasteStyleAction = {
name: 'Paste style',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard" }),
onSelect: () => {
et.buffer({ ...ctx.onBeforeAction?.(pasteStyleAction, 'paste'), action: 'paste', format: 'style' });
},
};
children.push(pasteStyleAction);
}
children.push(this.pasteAsMenu(ctx));
return {
id: 'paste-menu',
name: 'Paste',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "clipboard" }),
expand: 5,
children,
};
};
clipboardMenu = (ctx = {}) => {
const copyMenu = this.copyMenu(ctx);
const cutMenu = this.cutMenu(ctx);
const pasteMenu = this.pasteMenu(ctx);
cutMenu.sepBefore = true;
pasteMenu.sepBefore = true;
return {
name: 'Copy, cut, and paste',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "copy" }),
expand: 0,
sepBefore: true,
children: [copyMenu, cutMenu, pasteMenu],
};
};
getCaretMenu = () => {
return {
name: 'Inline text',
maxToolbarItems: 4,
children: [
this.getFormattingMenu(),
secondBrain(),
{
name: 'Annotations separator',
sep: true,
},
this.annotationsMenu(),
{
name: 'Style separator',
sep: true,
},
{
name: 'Typesetting',
expand: 4,
openOnTitleHov: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "typography" }),
onSelect: () => { },
children: [
{
name: 'Sans-serif',
iconBig: () => React.createElement(FontStyleButton, { kind: 'sans' }),
onSelect: () => { },
},
{
name: 'Serif',
iconBig: () => React.createElement(FontStyleButton, { kind: 'serif' }),
onSelect: () => { },
},
{
name: 'Slab',
icon: () => React.createElement(FontStyleButton, { kind: 'slab', size: 16 }),
iconBig: () => React.createElement(FontStyleButton, { kind: 'slab' }),
onSelect: () => { },
},
{
name: 'Monospace',
iconBig: () => React.createElement(FontStyleButton, { kind: 'mono' }),
onSelect: () => { },
},
// {
// name: 'Custom typeface separator',
// sep: true,
// },
{
name: 'Custom typeface',
expand: 10,
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "font-style" }),
children: [
{
name: 'Typeface',
// icon: () => <Iconista width={15} height={15} set="radix" icon="font-style" />,
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "font-family" }),
onSelect: () => { },
},
{
name: 'Text size',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "font-size" }),
onSelect: () => { },
},
{
name: 'Letter spacing',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "letter-spacing" }),
onSelect: () => { },
},
{
name: 'Word spacing',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "letter-spacing" }),
onSelect: () => { },
},
{
name: 'Caps separator',
sep: true,
},
{
name: 'Large caps',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "letter-case-uppercase" }),
onSelect: () => { },
},
{
name: 'Small caps',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "letter-case-lowercase" }),
onSelect: () => { },
},
],
},
],
},
{
name: 'Modify separator',
sep: true,
},
this.modifyMenu(),
this.clipboardMenu(),
{
name: 'Insert',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "between-vertical-end" }),
children: [
{
name: 'Smart chip',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "button" }),
children: [
{
name: 'Date',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "calendar" }),
onSelect: () => { },
},
{
name: 'AI chip',
icon: () => React.createElement(Iconista, { style: { color: 'purple' }, width: 16, height: 16, set: "tabler", icon: "brain" }),
onSelect: () => { },
},
{
name: 'Solana wallet',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "wallet" }),
onSelect: () => { },
},
{
name: 'Dropdown',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "dropdown-menu" }),
children: [
{
name: 'Create new',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "plus" }),
onSelect: () => { },
},
{
name: 'Document dropdowns separator',
sep: true,
},
{
name: 'Document dropdowns',
expand: 8,
onSelect: () => { },
children: [
{
name: 'Configuration 1',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "dropdown-menu" }),
onSelect: () => { },
},
{
name: 'Configuration 2',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "dropdown-menu" }),
onSelect: () => { },
},
],
},
{
name: 'Presets dropdowns separator',
sep: true,
},
{
name: 'Presets dropdowns',
expand: 8,
onSelect: () => { },
children: [
{
name: 'Project status',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "dropdown-menu" }),
onSelect: () => { },
},
{
name: 'Review status',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "dropdown-menu" }),
onSelect: () => { },
},
],
},
],
},
],
},
{
name: 'Link',
// icon: () => <Iconista width={15} height={15} set="lucide" icon="link" />,
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "link-2" }),
onSelect: () => { },
},
{
name: 'Reference',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "sewing-pin" }),
onSelect: () => { },
},
{
name: 'File',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "file" }),
onSelect: () => { },
},
{
name: 'Template',
text: 'building blocks',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "tabler", icon: "template" }),
children: [
{
name: 'Meeting notes',
onSelect: () => { },
},
{
name: 'Email draft (created by AI)',
onSelect: () => { },
},
{
name: 'Product roadmap',
onSelect: () => { },
},
{
name: 'Review tracker',
onSelect: () => { },
},
{
name: 'Project assets',
onSelect: () => { },
},
{
name: 'Content launch tracker',
onSelect: () => { },
},
],
},
{
name: 'On-screen keyboard',
icon: () => React.createElement(Iconista, { width: 15, height: 15, set: "radix", icon: "keyboard" }),
onSelect: () => { },
},
{
name: 'Emoji',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "smile-plus" }),
onSelect: () => { },
},
{
name: 'Special characters',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "omega" }),
onSelect: () => { },
},
{
name: 'Variable',
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "variable" }),
onSelect: () => { },
},
],
},
{
name: 'Developer tools',
danger: true,
icon: () => React.createElement(Iconista, { width: 16, height: 16, set: "lucide", icon: "square-chevron-right" }),
onSelect: () => { },
},
],
};
};
getSelectionMenu = () => {
return {
name: 'Selection menu',
// maxToolbarItems: 8,
more: true,
children: [
this.getFormattingMenu(),
this.annotationsMenu(),
this.modifyMenu(),
this.clipboardMenu(),
/*
secondBrain(),
{
name: 'Annotations separator',
sep: true,
},
{
name: 'Style separator',
sep: true,
},
{
name: 'Typesetting',
expand: 4,
openOnTitleHov: true,
icon: () => <Iconista width={16} height={16} set="tabler" icon="typography" />,
onSelect: () => {},
children: [
{
name: 'Sans-serif',
iconBig: () => <FontStyleButton kind={'sans'} />,
onSelect: () => {},
},
{
name: 'Serif',
iconBig: () => <FontStyleButton kind={'serif'} />,
onSelect: () => {},
},
{
name: 'Slab',
icon: () => <FontStyleButton kind={'slab'} size={16} />,
iconBig: () => <FontStyleButton kind={'slab'} />,
onSelect: () => {},
},
{
name: 'Monospace',
iconBig: () => <FontStyleButton kind={'mono'} />,
onSelect: () => {},
},
// {
// name: 'Custom typeface separator',
// sep: true,
// },
{
name: 'Custom typeface',
expand: 10,
icon: () => <Iconista width={15} height={15} set="radix" icon="font-style" />,
children: [
{
name: 'Typeface',
// icon: () => <Iconista width={15} height={15} set="radix" icon="font-style" />,
icon: () => <Iconista width={15} height={15} set="radix" icon="font-family" />,
onSelect: () => {},
},
{
name: 'Text size',
icon: () => <Iconista width={15} height={15} set="radix" icon="font-size" />,
onSelect: () => {},
},
{
name: 'Letter spacing',
icon: () => <Iconista width={15} height={15} set="radix" icon="letter-spacing" />,
onSelect: () => {},
},
{
name: 'Word spacing',
icon: () => <Iconista width={15} height={15} set="radix" icon="letter-spacing" />,
onSelect: () => {},
},
{
name: 'Caps separator',
sep: true,
},
{
name: 'Large caps',
icon: () => <Iconista width={15} height={15} set="radix" icon="letter-case-uppercase" />,
onSelect: () => {},
},
{
name: 'Small caps',
icon: () => <Iconista width={15} height={15} set="radix" icon="letter-case-lowercase" />,
onSelect: () => {},
},
],
},
],
},
{
name: 'Modify separator',
sep: true,
},
{
name: 'Modify',
expand: 3,
onSelect: () => {},
children: [
{
name: 'Pick layer',
right: () => (
<Code size={-1} gray>
9+
</Code>
),
more: true,
icon: () => <Iconista width={15} height={15} set="radix" icon="layers" />,
onSelect: () => {},
},
{
name: 'Erase formatting',
danger: true,
icon: () => <Iconista width={16} height={16} set="tabler" icon="eraser" />,
onSelect: () => {},
},
{
name: 'Delete all in range',
danger: true,
more: true,
icon: () => <Iconista width={16} height={16} set="tabler" icon="trash" />,
onSelect: () => {},
},
],
},
{
name: 'Clipboard separator',
sep: true,
},
{
name: 'Copy, cut, and paste',
// icon: () => <Iconista width={15} height={15} set="radix" icon="copy" />,
icon: () => <Iconista width={16} height={16} set="lucide" icon="copy" />,
expand: 0,
children: [
{
id: 'copy-menu',
name: 'Copy',
// icon: () => <Iconista width={15} height={15} set="radix" icon="copy" />,
icon: () => <Iconista width={15} height={15} set="radix" icon="clipboard-copy" />,
expand: 5,
children: [
{
name: 'Copy',
icon: () => <Iconista width={15} height={15} set="radix" icon="clipboard-copy" />,
onSelect: () => {},
},