diff --git a/public/notes.css b/public/notes.css index 41d485e9..a6dafb61 100644 --- a/public/notes.css +++ b/public/notes.css @@ -328,9 +328,6 @@ body.resizing-sidebar { cursor: col-resize; } body.resizing-sidebar-locked-min { cursor: e-resize; } body.resizing-sidebar-locked-max { cursor: w-resize; } -body.resizing-table-column { cursor: col-resize; } -body.resizing-table-row { cursor: row-resize; } - /* Note */ #content-container { @@ -360,6 +357,10 @@ body.resizing-table-row { cursor: row-resize; } #content ul { margin: 0; } #content ol { margin: 0 0 0 1em; } +#content img { + cursor: default; +} + #content pre { overflow: auto; padding: .5em; @@ -393,8 +394,21 @@ body.with-control #content a > * { pointer-events: none; } +body.with-control.resizing-img #content { + overflow: hidden; +} + /* My Notes classes */ +.my-notes-table-align-center { + margin-left: auto; + margin-right: auto; +} + +.my-notes-table-align-right { + margin-left: auto; +} + .my-notes-highlight { background: var(--highlight-background-color, yellow) !important; color: var(--highlight-text-color, black) !important; @@ -450,7 +464,7 @@ body.with-control #content a > * { } .table-resizing-div.active { - background: var(--table-resizing-line-color); + background: var(--resizing-line-color); } .table-column-resizing-div { @@ -469,6 +483,9 @@ body.with-control #content a > * { height: 4px; } +body.resizing-table-column { cursor: col-resize; } +body.resizing-table-row { cursor: row-resize; } + /* Locked */ body.locked #sidebar, @@ -565,20 +582,23 @@ body.with-command-palette #toolbar { background: var(--context-menu-background-color); color: var(--context-menu-text-color); cursor: pointer; +} + +#context-menu .action:not(.group) { padding: .8em; } -#context-menu .action:hover:not(.disabled) { +#context-menu .action:hover:not(.group):not(.disabled) { background: var(--context-menu-hover-background-color); color: var(--context-menu-hover-text-color); } -#context-menu .action:first-child { +#context-menu .action:not(.inline):first-child { border-top-left-radius: 5px; border-top-right-radius: 5px; } -#context-menu .action:last-child { +#context-menu .action:not(.inline):last-child { border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } @@ -588,6 +608,36 @@ body.with-command-palette #toolbar { color: var(--context-menu-disabled-text-color); } +#context-menu .action > svg { + height: 1em; + fill: var(--context-menu-text-color); +} + +#context-menu .action.group { + display: flex; +} + +#context-menu .action.group:first-child > .action:first-child { + border-top-left-radius: 5px; +} + +#context-menu .action.group:first-child > .action:last-child { + border-top-right-radius: 5px; +} + +#context-menu .action.group:last-child > .action:first-child { + border-bottom-left-radius: 5px; +} + +#context-menu .action.group:last-child > .action:last-child { + border-bottom-right-radius: 5px; +} + +#context-menu .action.inline { + display: flex; + align-items: center; +} + /* Toolbar */ #toolbar { diff --git a/public/themes/dark.css b/public/themes/dark.css index 7a6f8340..9a34a79a 100644 --- a/public/themes/dark.css +++ b/public/themes/dark.css @@ -88,7 +88,7 @@ --table-border: 3px solid #454545; --table-td-border: 1px solid #222; --table-td-heading-background-color: #222; - --table-resizing-line-color: #0000ff; + --resizing-line-color: #0000ff; /* Command palette */ diff --git a/public/themes/light.css b/public/themes/light.css index 78c659f0..069a93c8 100644 --- a/public/themes/light.css +++ b/public/themes/light.css @@ -88,7 +88,7 @@ --table-border: 3px solid #171717; --table-td-border: 1px solid silver; --table-td-heading-background-color: #dddddd; - --table-resizing-line-color: #0000ff; + --resizing-line-color: #0000ff; /* Command palette */ diff --git a/src/notes.tsx b/src/notes.tsx index 7c2ceb7c..db1be750 100644 --- a/src/notes.tsx +++ b/src/notes.tsx @@ -25,7 +25,7 @@ import { ContentNote } from "notes/components/content/common"; import __CommandPalette, { CommandPaletteProps } from "notes/components/CommandPalette"; import __Toolbar from "notes/components/Toolbar"; -import __ContextMenu, { ContextMenuProps } from "notes/components/ContextMenu"; +import NoteContextMenu, { NoteContextMenuProps } from "notes/components/NoteContextMenu"; import __RenameNoteModal, { RenameNoteModalProps } from "notes/components/modals/RenameNoteModal"; import __DeleteNoteModal, { DeleteNoteModalProps } from "notes/components/modals/DeleteNoteModal"; import __NewNoteModal, { NewNoteModalProps } from "notes/components/modals/NewNoteModal"; @@ -102,12 +102,18 @@ const Notes = (): h.JSX.Element | null => { const [autoSync, setAutoSync] = useState(false); // Modals - const [contextMenuProps, setContextMenuProps] = useState(null); + const [noteContextMenuProps, setNoteContextMenuProps] = useState(null); const [renameNoteModalProps, setRenameNoteModalProps] = useState(null); const [deleteNoteModalProps, setDeleteNoteModalProps] = useState(null); const [newNoteModalProps, setNewNoteModalProps] = useState(null); const [commandPaletteProps, setCommandPaletteProps] = useState(null); + useEffect(() => { + if (noteContextMenuProps) { + render(, document.getElementById("context-menu-container")!); + } + }, [noteContextMenuProps]); + useEffect(() => { chrome.runtime.getPlatformInfo((platformInfo) => setOs(platformInfo.os === "mac" ? "mac" : "other")); chrome.tabs.getCurrent((currentTab) => currentTab && setTabId(currentTab.id)); @@ -269,7 +275,7 @@ const Notes = (): h.JSX.Element | null => { // Update note content if updated from background const setBy: string | undefined = changes.setBy && changes.setBy.newValue; if ( - (setBy && !setBy.startsWith(`${tabId}-`)) // expecting "worker-*" or "sync-*" + (setBy && !setBy.startsWith(`${tabId}-`)) // can be other tab, "worker-*", or "sync-*" && (newActive in oldNotes) && (newActive in newNotes) && (newNotes[newActive].content !== oldNotes[newActive].content) @@ -382,15 +388,29 @@ const Notes = (): h.JSX.Element | null => { document.body.classList.toggle("focus", focus); }, [focus]); - // Hide context menu on click anywhere + const removeContextMenus = useCallback(() => { + setNoteContextMenuProps(null); + + const container = document.getElementById("context-menu-container"); + if (container) { + render("", container); + } + + return true; + }, []); + + // Remove context menus on click anywhere useEffect(() => { - document.addEventListener("click", (event) => { - if ((event.target as Element).closest("#context-menu") === null) { - setContextMenuProps(null); - } + document.addEventListener("click", () => { + removeContextMenus(); }); }, []); + // Remove context menus on changed active note + useEffect(() => { + removeContextMenus(); + }, [notesProps.active]); + // Activate note useEffect(() => { if (!notesProps.active) { @@ -425,7 +445,7 @@ const Notes = (): h.JSX.Element | null => { keyboardShortcuts.register(os); keyboardShortcuts.subscribe(KeyboardShortcut.OnEscape, () => { - setContextMenuProps(null); + removeContextMenus(); setCommandPaletteProps((prev) => { if (prev) { range.restore(); @@ -443,7 +463,13 @@ const Notes = (): h.JSX.Element | null => { keyboardShortcuts.subscribe(KeyboardShortcut.OnSync, () => sendMessage(MessageType.SYNC)); }, [os]); + const [setOnControlHandler] = useKeyboardShortcut(KeyboardShortcut.OnControl); + useEffect(() => { + setOnControlHandler(() => { + document.body.classList.add("with-control"); + }); + window.addEventListener("blur", () => { document.body.classList.remove("with-control"); }); @@ -573,11 +599,10 @@ const Notes = (): h.JSX.Element | null => { active={notesProps.active} width={sidebarWidth} onActivateNote={handleOnActivateNote} - onNoteContextMenu={(noteName, x, y) => setContextMenuProps({ + onNoteContextMenu={(noteName, x, y) => removeContextMenus() && setNoteContextMenuProps({ x, y, onRename: () => { - setContextMenuProps(null); setRenameNoteModalProps({ noteName, validate: (newNoteName: string) => newNoteName.length > 0 && newNoteName !== noteName && !(newNoteName in notesProps.notes), @@ -589,7 +614,6 @@ const Notes = (): h.JSX.Element | null => { }); }, onDelete: () => { - setContextMenuProps(null); setDeleteNoteModalProps({ noteName, onCancel: () => setDeleteNoteModalProps(null), @@ -601,29 +625,25 @@ const Notes = (): h.JSX.Element | null => { }, locked: notesProps.notes[noteName].locked ?? false, onToggleLocked: () => { - setContextMenuProps(null); if (tabId && notesRef.current) { setLocked(noteName, !(notesProps.notes[noteName].locked ?? false), tabId, notesRef.current); } }, pinned: !!notesProps.notes[noteName].pinnedTime, onTogglePinnedTime: () => { - setContextMenuProps(null); if (tabId && notesRef.current) { setPinnedTime( noteName, - (notesProps.notes[noteName].pinnedTime ?? undefined) ? undefined : new Date().toISOString(), + notesProps.notes[noteName].pinnedTime ? undefined : new Date().toISOString(), tabId, notesRef.current, ); } }, onDuplicate: () => { - setContextMenuProps(null); duplicateNote(noteName); }, onExport: () => { - setContextMenuProps(null); exportNote(noteName); }, })} @@ -678,10 +698,6 @@ const Notes = (): h.JSX.Element | null => { /> )} - {contextMenuProps && ( - <__ContextMenu {...contextMenuProps} /> - )} - {renameNoteModalProps && ( <__RenameNoteModal {...renameNoteModalProps} /> @@ -703,6 +719,7 @@ const Notes = (): h.JSX.Element | null => { )} +
); diff --git a/src/notes/components/ContextMenu.tsx b/src/notes/components/ContextMenu.tsx index b6ef5141..c339e3d8 100644 --- a/src/notes/components/ContextMenu.tsx +++ b/src/notes/components/ContextMenu.tsx @@ -1,59 +1,46 @@ -import { h } from "preact"; +import { h, ComponentChildren } from "preact"; import { useState, useRef, useEffect } from "preact/hooks"; -import clsx from "clsx"; -import { t } from "i18n"; export interface ContextMenuProps { x: number y: number - onRename: () => void - onDelete: () => void - onToggleLocked: () => void - onTogglePinnedTime: () => void - onDuplicate: () => void - onExport: () => void - locked: boolean - pinned: boolean + children: ComponentChildren } -const ContextMenu = ({ - x, y, - onRename, onDelete, onToggleLocked, onTogglePinnedTime, onDuplicate, onExport, - locked, pinned, -}: ContextMenuProps): h.JSX.Element => { - const [offsetHeight, setOffsetHeight] = useState(0); +interface Offsets { + offsetHeight: number + offsetWidth: number +} + +const ContextMenu = ({ x, y, children }: ContextMenuProps): h.JSX.Element => { const ref = useRef(null); + const [offsets, setOffsets] = useState(undefined); useEffect(() => { - if (!ref.current) { + if (!ref.current || offsets) { return; } - if (offsetHeight) { - return; // offsetHeight already set - } - - setOffsetHeight(ref.current.offsetHeight); - }, [ref.current, offsetHeight]); + setOffsets({ + offsetHeight: ref.current.offsetHeight, + offsetWidth: ref.current.offsetWidth, + }); + }, [ref.current]); return (
= window.innerHeight) ? "1em" : "", + style={offsets ? { + left: (x + offsets.offsetWidth < window.innerWidth) ? `${x}px` : undefined, + right: (x + offsets.offsetWidth >= window.innerWidth) ? "1em" : undefined, + top: (y + offsets.offsetHeight < window.innerHeight) ? `${y}px` : undefined, + bottom: (y + offsets.offsetHeight >= window.innerHeight) ? "1em" : undefined, } : { - opacity: 0, // offsetHeight NOT set, yet + opacity: 0, // offsets NOT known, yet }} > -
!locked && onRename()}>{t("Rename")}
-
!locked && onDelete()}>{t("Delete")}
-
onToggleLocked()}>{locked ? t("Unlock") : t("Lock")}
-
onTogglePinnedTime()}>{pinned ? t("Unpin") : t("Pin")}
-
onDuplicate()}>{t("Duplicate")}
-
onExport()}>{t("Export")}
+ {children}
); }; diff --git a/src/notes/components/NoteContextMenu.tsx b/src/notes/components/NoteContextMenu.tsx new file mode 100644 index 00000000..d393c36e --- /dev/null +++ b/src/notes/components/NoteContextMenu.tsx @@ -0,0 +1,34 @@ +import { h } from "preact"; +import clsx from "clsx"; +import { t } from "i18n"; +import ContextMenu from "./ContextMenu"; + +export interface NoteContextMenuProps { + x: number + y: number + onRename: () => void + onDelete: () => void + onToggleLocked: () => void + onTogglePinnedTime: () => void + onDuplicate: () => void + onExport: () => void + locked: boolean + pinned: boolean +} + +const NoteContextMenu = ({ + x, y, + onRename, onDelete, onToggleLocked, onTogglePinnedTime, onDuplicate, onExport, + locked, pinned, +}: NoteContextMenuProps): h.JSX.Element => ( + +
!locked && onRename()}>{t("Rename")}
+
!locked && onDelete()}>{t("Delete")}
+
onToggleLocked()}>{locked ? t("Unlock") : t("Lock")}
+
onTogglePinnedTime()}>{pinned ? t("Unpin") : t("Pin")}
+
onDuplicate()}>{t("Duplicate")}
+
onExport()}>{t("Export")}
+
+); + +export default NoteContextMenu; diff --git a/src/notes/components/SidebarNotes.tsx b/src/notes/components/SidebarNotes.tsx index f5af52e0..7455aef6 100644 --- a/src/notes/components/SidebarNotes.tsx +++ b/src/notes/components/SidebarNotes.tsx @@ -7,7 +7,6 @@ import { SidebarNote } from "notes/adapters"; import clsx from "clsx"; import SVG from "notes/components/SVG"; import svgs from "svg"; -import { useKeyboardShortcut, KeyboardShortcut } from "notes/components/hooks/use-keyboard-shortcut"; import sendMessage from "shared/messages/send"; export interface SidebarNotesProps { @@ -41,8 +40,6 @@ const SidebarNotes = ({ const [dragOverNote, setDragOverNote] = useState(null); const [dragOverNoteConfirmation, setDragOverNoteConfirmation] = useState(null); - const [setOnControlHandler] = useKeyboardShortcut(KeyboardShortcut.OnControl); - const [enteredNote, setEnteredNote] = useState(null); const enteredNoteRef = useRef(null); enteredNoteRef.current = enteredNote; @@ -68,11 +65,6 @@ const SidebarNotes = ({ useEffect(() => setNotes(sidebarNotes), [sidebarNotes]); useEffect(openEnteredNote, [enteredNote]); - useEffect(() => setOnControlHandler(() => { - document.body.classList.add("with-control"); - openEnteredNote(); - }), [openEnteredNote]); - useEffect(() => setOnDraggingNoteOriginator(draggedNote ? id : undefined), [id, draggedNote]); return ( diff --git a/src/notes/components/TableContextMenu.tsx b/src/notes/components/TableContextMenu.tsx new file mode 100644 index 00000000..44aececf --- /dev/null +++ b/src/notes/components/TableContextMenu.tsx @@ -0,0 +1,35 @@ +import { h } from "preact"; +import SVG from "notes/components/SVG"; +import svgs from "svg"; +import ContextMenu from "./ContextMenu"; + +export interface TableContextMenuProps { + x: number + y: number + onAlignLeft: () => void + onAlignCenter: () => void + onAlignRight: () => void +} + +const TableContextMenu = ({ + x, y, + onAlignLeft, onAlignCenter, onAlignRight, +}: TableContextMenuProps): h.JSX.Element => ( + +
+
+ +
+ +
+ +
+ +
+ +
+
+
+); + +export default TableContextMenu; diff --git a/src/notes/components/Toolbar.tsx b/src/notes/components/Toolbar.tsx index 7febd029..8dda3d46 100644 --- a/src/notes/components/Toolbar.tsx +++ b/src/notes/components/Toolbar.tsx @@ -6,7 +6,8 @@ import { Os, Note } from "shared/storage/schema"; import { t } from "i18n"; import capitalize from "shared/string/capitalize"; import { HIGHLIGHT_COLORS } from "notes/commands/highlight"; -import { reinitTables } from "notes/content/table"; +import initContent from "notes/content/init"; +import { dispatchNoteEdited } from "notes/events"; import SVG from "notes/components/SVG"; import svgs from "svg"; import { @@ -24,16 +25,9 @@ import Tooltip from "./Tooltip"; import NoteInfo from "./NoteInfo"; import EmbedHtmlModal, { EmbedHtmlModalProps } from "./modals/EmbedHtmlModal"; -const callback = () => { - const event = new Event("editnote"); - document.dispatchEvent(event); -}; - const tableCallback = () => { - callback(); - reinitTables({ - onResize: callback, - }); + dispatchNoteEdited(); + initContent(); }; interface ToolbarProps { @@ -150,12 +144,12 @@ const Toolbar = ({
-
table.toggleHeadingRow(callback)}> +
table.toggleHeadingRow(tableCallback)}>
-
table.toggleHeadingColumn(callback)}> +
table.toggleHeadingColumn(tableCallback)}>
@@ -178,7 +172,7 @@ const Toolbar = ({
highlight(`my-notes-text-color-${color}`, callback)} + onClick={() => highlight(`my-notes-text-color-${color}`, dispatchNoteEdited)} > A
@@ -187,7 +181,7 @@ const Toolbar = ({
highlight("my-notes-text-color-auto", callback)} + onClick={() => highlight("my-notes-text-color-auto", dispatchNoteEdited)} > Auto
@@ -195,7 +189,7 @@ const Toolbar = ({
highlight("my-notes-highlight", callback)} + onClick={() => highlight("my-notes-highlight", dispatchNoteEdited)} > Hi
diff --git a/src/notes/components/content/ContentHtml.tsx b/src/notes/components/content/ContentHtml.tsx index b609ebb3..115db466 100644 --- a/src/notes/components/content/ContentHtml.tsx +++ b/src/notes/components/content/ContentHtml.tsx @@ -5,9 +5,10 @@ import { KeyboardShortcut } from "notes/keyboard-shortcuts"; import { useKeyboardShortcut } from "notes/components/hooks/use-keyboard-shortcut"; import __range from "notes/range"; import { insideListItem } from "notes/content/list"; -import { reinitTables } from "notes/content/table"; +import initContent from "notes/content/init"; +import { reattachOnNoteEdited } from "notes/events"; import { commands, InsertTabFactory } from "../../commands"; -import { ContentProps, reattachEditNote } from "./common"; +import { ContentProps } from "./common"; import { isImageFile } from "../image/read-image"; import { dropImage } from "../image/drop-image"; @@ -78,12 +79,7 @@ const ContentHtml = ({ if (contentRef.current) { contentRef.current.innerHTML = note.initialContent; focus(contentRef.current); - reinitTables({ - onResize: () => { - const event = new Event("editnote"); - document.dispatchEvent(event); - }, - }); + initContent(); } }, [note.active, note.initialContent]); @@ -91,12 +87,13 @@ const ContentHtml = ({ if (note.active && contentRef.current) { const content = contentRef.current.innerHTML; onEdit(note.active, content); + initContent(); } }, [note.active]); // Toolbar controls (e.g. TABLE_INSERT) can change #content.innerHTML. - // To save the changed content, "editnote" event is triggered from Toolbar. - useEffect(() => reattachEditNote(onInput), [onInput]); + // To save the changed content, "NoteEdited" event is triggered from Toolbar. + useEffect(() => reattachOnNoteEdited(onInput), [onInput]); const [onUnderline] = useKeyboardShortcut(KeyboardShortcut.OnUnderline); const [onStrikethrough] = useKeyboardShortcut(KeyboardShortcut.OnStrikethrough); diff --git a/src/notes/components/content/ContentText.tsx b/src/notes/components/content/ContentText.tsx index 3466f5ee..ce1c691f 100644 --- a/src/notes/components/content/ContentText.tsx +++ b/src/notes/components/content/ContentText.tsx @@ -2,8 +2,9 @@ import { h } from "preact"; import { useCallback, useEffect, useRef } from "preact/hooks"; import { KeyboardShortcut } from "notes/keyboard-shortcuts"; import { useKeyboardShortcut } from "notes/components/hooks/use-keyboard-shortcut"; +import { reattachOnNoteEdited } from "notes/events"; import { InsertTabFactory } from "../../commands"; -import { ContentProps, reattachEditNote } from "./common"; +import { ContentProps } from "./common"; const focus = (content: HTMLTextAreaElement) => content && window.setTimeout(() => { content.focus(); @@ -31,7 +32,7 @@ const ContentText = ({ } }, [note.active]); - useEffect(() => reattachEditNote(onInput), [onInput]); + useEffect(() => reattachOnNoteEdited(onInput), [onInput]); useEffect(() => setIndentOnTabHandlerOnTab( indentOnTab diff --git a/src/notes/components/content/common.ts b/src/notes/components/content/common.ts index c9cedf89..fbc2fe95 100644 --- a/src/notes/components/content/common.ts +++ b/src/notes/components/content/common.ts @@ -11,10 +11,3 @@ export interface ContentProps { indentOnTab: boolean tabSize: number } - -let latestCb: () => void; -export const reattachEditNote = (cb: () => void) => { - document.removeEventListener("editnote", latestCb); - latestCb = cb; - document.addEventListener("editnote", latestCb); -}; diff --git a/src/notes/content/__tests__/table.test.ts b/src/notes/content/__tests__/table.test.ts index 5c804902..3deae2f6 100644 --- a/src/notes/content/__tests__/table.test.ts +++ b/src/notes/content/__tests__/table.test.ts @@ -3,7 +3,7 @@ import { getAllCellsInColumn, MakeTableResizableProps, makeTableResizable, - reinitTable, + initTable, } from "../table"; // A B C @@ -79,7 +79,7 @@ test("getAllCellsInColumn() returns cells in a column", () => { expect(getAllCellsInColumn(table, 99).map((cell) => cell.innerHTML)).toEqual([]); }); -describe("reinitTable", () => { +describe("initTable", () => { let dom: JSDOM; let table: HTMLTableElement; let resizableProps: MakeTableResizableProps; @@ -96,9 +96,11 @@ describe("reinitTable", () => { onResize: () => { }, }; makeTableResizableFunction = jest.fn(); - reinitTable({ + initTable({ + table, resizableProps, makeTableResizableFunction, + onContextMenu: () => {}, }); }); diff --git a/src/notes/content/img.ts b/src/notes/content/img.ts new file mode 100644 index 00000000..2bd8f437 --- /dev/null +++ b/src/notes/content/img.ts @@ -0,0 +1,32 @@ +import { dispatchNoteEdited } from "notes/events"; + +const MIN_ZOOM = 0.1; +const ZOOM_STEP = 0.003; + +/* eslint-disable no-param-reassign */ +const makeImgZoomable = (img: HTMLImageElement) => { + img.onwheel = (event) => { + if (!document.body.classList.contains("with-control")) { + return; + } + + document.body.classList.add("resizing-img"); + + const zoomChange = ZOOM_STEP * event.deltaY; + // @ts-ignore + const parsedZoom = parseFloat(img.style.zoom); + // @ts-ignore + img.style.zoom = Math.max(MIN_ZOOM, (Number.isNaN(parsedZoom) ? 1 : parsedZoom) + zoomChange); + }; + img.ondblclick = () => { + // @ts-ignore + img.style.zoom = ""; + dispatchNoteEdited(); + }; +}; + +// eslint-disable-next-line import/prefer-default-export +export const initImgs = () => { + const imgs = document.querySelectorAll("body > #content-container > #content img"); + imgs.forEach(makeImgZoomable); +}; diff --git a/src/notes/content/init.ts b/src/notes/content/init.ts new file mode 100644 index 00000000..b4dcc37e --- /dev/null +++ b/src/notes/content/init.ts @@ -0,0 +1,10 @@ +import { initImgs } from "./img"; +import { initTables } from "./table"; +import renderTableContextMenu from "./render-table-context-menu"; + +export default () => { + initImgs(); + initTables({ + contextMenuRenderFunction: renderTableContextMenu, + }); +}; diff --git a/src/notes/content/render-table-context-menu.tsx b/src/notes/content/render-table-context-menu.tsx new file mode 100644 index 00000000..3eddd486 --- /dev/null +++ b/src/notes/content/render-table-context-menu.tsx @@ -0,0 +1,27 @@ +import { h, render } from "preact"; +import TableContextMenu from "notes/components/TableContextMenu"; +import { dispatchNoteEdited } from "notes/events"; + +const alignTable = (table: HTMLTableElement, alignment: "left" | "center" | "right") => { + table.classList.remove( + "my-notes-table-align-center", + "my-notes-table-align-right", + ); + + if (alignment === "center" || alignment === "right") { + table.classList.add(`my-notes-table-align-${alignment}`); + } + + dispatchNoteEdited(); +}; + +export default (table: HTMLTableElement, event: MouseEvent) => render( + alignTable(table, "left")} + onAlignCenter={() => alignTable(table, "center")} + onAlignRight={() => alignTable(table, "right")} + />, + document.getElementById("context-menu-container")!, +); diff --git a/src/notes/content/table.ts b/src/notes/content/table.ts index 58c84cd7..96c549fc 100644 --- a/src/notes/content/table.ts +++ b/src/notes/content/table.ts @@ -1,4 +1,6 @@ /* eslint-disable no-param-reassign */ +import { dispatchNoteEdited } from "notes/events"; + const TABLE_RESIZING_DIV_CLASSNAME = "table-resizing-div"; const TABLE_COLUMN_RESIZING_DIV_CLASSNAME = "table-column-resizing-div"; const TABLE_ROW_RESIZING_DIV_CLASSNAME = "table-row-resizing-div"; @@ -188,12 +190,16 @@ export const makeTableResizable = ({ } }; -interface ReinitTableProps { +interface InitTableProps { + table: HTMLTableElement resizableProps: MakeTableResizableProps makeTableResizableFunction: (props: MakeTableResizableProps) => void + onContextMenu: (event: MouseEvent) => void } -export const reinitTable = ({ resizableProps, makeTableResizableFunction }: ReinitTableProps): void => { +export const initTable = ({ + table, resizableProps, makeTableResizableFunction, onContextMenu, +}: InitTableProps): void => { const cells = resizableProps.table.querySelectorAll("td"); cells.forEach((oneCell) => { oneCell.onmouseleave = null; @@ -202,21 +208,33 @@ export const reinitTable = ({ resizableProps, makeTableResizableFunction }: Rein }); makeTableResizableFunction(resizableProps); + table.oncontextmenu = onContextMenu; }; -interface ReinitTablesProps { - onResize: OnResizeCallback +interface InitTables { + contextMenuRenderFunction: (table: HTMLTableElement, event: MouseEvent) => void } -export const reinitTables = ({ onResize }: ReinitTablesProps): void => { +export const initTables = ({ + contextMenuRenderFunction, +}: InitTables): void => { const tables = document.querySelectorAll("body > #content-container > #content table"); - tables.forEach((table) => reinitTable({ + tables.forEach((table) => initTable({ + table, resizableProps: { document, computedStyleFunction: window.getComputedStyle, table, - onResize, + onResize: dispatchNoteEdited, }, makeTableResizableFunction: makeTableResizable, + onContextMenu: (event) => { + if (!document.body.classList.contains("with-control")) { + return; + } + + event.preventDefault(); + contextMenuRenderFunction(table, event); + }, })); }; diff --git a/src/notes/events/index.ts b/src/notes/events/index.ts new file mode 100644 index 00000000..aa7f6827 --- /dev/null +++ b/src/notes/events/index.ts @@ -0,0 +1,12 @@ +const NoteEdited = "NoteEdited"; + +export const dispatchNoteEdited = () => { + document.dispatchEvent(new Event(NoteEdited)); +}; + +let latestCb: () => void; +export const reattachOnNoteEdited = (cb: () => void) => { + document.removeEventListener(NoteEdited, latestCb); + latestCb = cb; + document.addEventListener(NoteEdited, latestCb); +}; diff --git a/src/notes/keyboard-shortcuts.ts b/src/notes/keyboard-shortcuts.ts index 7619f204..8c7a9201 100644 --- a/src/notes/keyboard-shortcuts.ts +++ b/src/notes/keyboard-shortcuts.ts @@ -1,4 +1,5 @@ import { Os } from "shared/storage/schema"; +import { dispatchNoteEdited } from "./events"; const isMac = (os: Os) => os === "mac"; @@ -279,6 +280,10 @@ const keydown = (os: Os) => document.addEventListener("keydown", (event) => { const keyup = () => document.addEventListener("keyup", () => { document.body.classList.remove("with-control"); + if (document.body.classList.contains("resizing-img")) { + document.body.classList.remove("resizing-img"); + dispatchNoteEdited(); + } }); const register = (os: Os): void => {