diff --git a/package.json b/package.json index bb445bf..0333fab 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "class-variance-authority": "^0.7.0", "date-fns": "^3.3.1", "electron": "^28.2.0", - "emotion": "^10.0.27", "esbuild": "^0.20.0", "evergreen-ui": "^7.1.9", "klaw": "^3.0.0", diff --git a/src/components/Sidesheet.tsx b/src/components/Sidesheet.tsx index b40e74f..02ed872 100644 --- a/src/components/Sidesheet.tsx +++ b/src/components/Sidesheet.tsx @@ -21,6 +21,7 @@ const SheetOverlay = React.forwardRef< // Initial use case is for side bar (journal selection) -- imo looks better when backgroud is not faded / covered w/ animation; may want to re-enable // for other use cases // "fixed inset-0 z-50 bg-slate-400 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", + "drag-none", className, )} {...props} @@ -30,7 +31,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300", + "drag-none fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300", { variants: { side: { diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 4ddce65..d64a144 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -25,6 +25,7 @@ import { ExternalLink, Eye, FileCode, + FilePenLineIcon, Folder, FolderArchive, FolderCheck, @@ -203,7 +204,7 @@ export const Icons = { commentAdd: MessageSquarePlus, delete: Trash2, dragHandle: GripVertical, - editing: Edit2, + editing: FilePenLineIcon, emoji: Smile, externalLink: ExternalLink, folder: Folder, diff --git a/src/electron/index.js b/src/electron/index.js index 4c5ae22..93a3f4c 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -193,6 +193,12 @@ function createWindow() { mainWindow = new BrowserWindow({ width, height: 600, + + // Hides the default (empty) window title + // + titleBarStyle: "hidden", + + trafficLightPosition: { x: 10, y: 16 }, webPreferences: { nodeIntegration: false, sandbox: false, diff --git a/src/index.css b/src/index.css index 3a3024e..3a2397a 100644 --- a/src/index.css +++ b/src/index.css @@ -26,6 +26,7 @@ --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; + /* --accent: 255, 89%, 62%; */ --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; diff --git a/src/layout.tsx b/src/layout.tsx index 26619f3..5f4269e 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -1,34 +1,12 @@ import React, { PropsWithChildren } from "react"; import { Pane, Tablist, Tab } from "evergreen-ui"; import ErrorBoundary from "./error"; -import { NavLink, useLocation } from "react-router-dom"; interface Props2 { children: any; } -function classnameFunc({ isActive }: any) { - return isActive ? "link-active" : "link-inactive"; -} - export default function Layout(props: Props2) { - // I was too lazy to have each top-level route wrap itself in the right - // layout, since only the edit view(s) don't use the normal layout - // hence we have this hack here. What could go wrong? - const location = useLocation(); - if (location.pathname.startsWith("/edit")) { - return ( - - {props.children} - - ); - } - return ( - - - - chronicles - - - - - documents - - - preferences - - - - - {props.children} - + {props.children} ); diff --git a/src/titlebar/macos.tsx b/src/titlebar/macos.tsx new file mode 100644 index 0000000..e72ec1c --- /dev/null +++ b/src/titlebar/macos.tsx @@ -0,0 +1,20 @@ +import React, { PropsWithChildren } from "react"; +import { cn } from "@udecode/cn"; + +interface Props extends PropsWithChildren { + className?: string; +} + +export default function Titlebar({ children, className }: Props) { + return ( +
+ {children} +
+ ); +} diff --git a/src/typography.css b/src/typography.css index 914ff05..37a66b1 100644 --- a/src/typography.css +++ b/src/typography.css @@ -235,7 +235,7 @@ html { "Segoe UI Emoji", "Segoe UI Symbol"; box-sizing: border-box; - overflow-y: scroll; + overflow: hidden; line-height: 1.6; } diff --git a/src/views/documents/Layout.tsx b/src/views/documents/Layout.tsx index e2608c6..2edd107 100644 --- a/src/views/documents/Layout.tsx +++ b/src/views/documents/Layout.tsx @@ -1,10 +1,19 @@ import React from "react"; -import { Pane, IconButton, FolderOpenIcon } from "evergreen-ui"; +import { + Pane, + IconButton, + FolderOpenIcon, + EditIcon, + SettingsIcon, + PanelStatsIcon, +} from "evergreen-ui"; import SearchDocuments from "./search"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { SearchStore } from "./SearchStore"; import JournalSelectionSidebar from "./sidebar/Sidebar"; import { SheetTrigger } from "../../components/Sidesheet"; +import Titlebar from "../../titlebar/macos"; +import * as Base from "../layout"; interface Props { store: SearchStore; @@ -14,33 +23,54 @@ interface Props { export function Layout(props: Props) { const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); + const navigate = useNavigate(); return ( - - + + - + Select Journals + + navigate("/documents/edit/new")} + marginRight={8} + > + Create new note + + - - - Create new - - {props.children} - + navigate("/preferences")} + marginLeft={8} + > + Preferences + + + + {props.children} + ); } - -export type SidebarProps = React.PropsWithChildren<{ - isShown: boolean; - setIsShown: (isShown: boolean) => void; - search: SearchStore; -}>; diff --git a/src/views/documents/search/index.tsx b/src/views/documents/search/index.tsx index 392761a..a13be62 100644 --- a/src/views/documents/search/index.tsx +++ b/src/views/documents/search/index.tsx @@ -35,6 +35,7 @@ const SearchDocuments = (props: Props) => { return ( void; + search: SearchStore; +}>; /** * Sidebar for selecting journals or tags to search by. @@ -29,7 +36,7 @@ export default observer(function JournalSelectionSidebar(props: SidebarProps) { {props.children} { if (!store.shouldEscapeClose) { diff --git a/src/views/documents/sidebar/sidebar-styles.css b/src/views/documents/sidebar/sidebar-styles.css index 3b12efc..41b539d 100644 --- a/src/views/documents/sidebar/sidebar-styles.css +++ b/src/views/documents/sidebar/sidebar-styles.css @@ -2,6 +2,8 @@ @import "@radix-ui/colors/mauve.css"; @import "@radix-ui/colors/violet.css"; +/* todo: move ContextMenu stuffs to their own stylesheet */ + .ContextMenuTrigger { /* display: block; border: 2px white dashed; diff --git a/src/views/edit/FrontMatter.tsx b/src/views/edit/FrontMatter.tsx new file mode 100644 index 0000000..00d335b --- /dev/null +++ b/src/views/edit/FrontMatter.tsx @@ -0,0 +1,168 @@ +import { Popover, Menu, Position, TagInput } from "evergreen-ui"; +import React from "react"; +import { DayPicker } from "react-day-picker"; +import { useNavigate } from "react-router-dom"; +import { JournalResponse } from "../../preload/client/journals"; +import { useSearchStore } from "../documents/SearchStore"; +import { TagTokenParser } from "../documents/search/parsers/tag"; +import { EditableDocument } from "./EditableDocument"; +import { observer } from "mobx-react-lite"; + +const FrontMatter = observer( + ({ + document, + journals, + }: { + document: EditableDocument; + journals: JournalResponse[]; + }) => { + function onAddTag(tokens: string[]) { + if (tokens.length > 1) { + // https://evergreen.segment.com/components/tag-input + // Documents say this is single value, Type says array + // Testing says array but with only one value... unsure how multiple + // values end up in the array. + console.warn( + "TagInput.onAdd called with > 1 token? ", + tokens, + "ignoring extra tokens", + ); + } + + let tag = new TagTokenParser().parse(tokens[0])?.value; + if (!tag) return; + + if (!document.tags.includes(tag)) { + document.tags.push(tag); + document.save(); + } + } + + function onRemoveTag(tag: string | React.ReactNode, idx: number) { + if (typeof tag !== "string") return; + document.tags = document.tags.filter((t) => t !== tag); + document.save(); + } + + // Autofocus the heading input + const onInputRendered = React.useCallback( + (inputElement: HTMLInputElement) => { + if (inputElement) { + // After experimenting, unsure why the delay is helpful. + // https://blog.maisie.ink/react-ref-autofocus/ + setTimeout(() => inputElement.focus(), 200); + inputElement.focus(); + } + }, + [], + ); + + // todo: move this to view model + function getName(journalId?: string) { + const journal = journals?.find((j) => j.id === journalId); + return journal ? journal.name : "Unknown journal"; + } + + function makeOptions(close: any) { + return journals.map((j: any) => { + return ( + { + document.journalId = j.id; + close(); + }} + > + {j.name} + + ); + }); + } + + function journalPicker() { + return ( + ( +
+ + {makeOptions(close)} + +
+ )} + > + + {getName(document.journalId)} + +
+ ); + } + + function onDayPick(day: Date, callback: () => void) { + document.createdAt = day.toISOString(); + callback(); + } + + // tests: when changing date, documents date is highlighted + // when changing date, currently selected date's month is the active one + // document auto-saves when changing date + function datePicker() { + return ( + ( +
+ onDayPick(day, close)} + mode="single" + /> +
+ )} + > + + {document.createdAt.slice(0, 10)} + +
+ ); + } + + return ( + <> + {/* Document title */} +
+ (document.title = e.target.value)} + value={document.title || ""} // OR '' prevents react complaining about uncontrolled component + placeholder="Untitled document" + /> +
+ + {/* Date / Journal dropdown */} +
+ {datePicker()} +  in  + {journalPicker()} +
+ + {/* Tags */} +
+ +
+ + ); + }, +); + +export default FrontMatter; diff --git a/src/views/edit/PlateContainer.tsx b/src/views/edit/PlateContainer.tsx new file mode 100644 index 0000000..44cb992 --- /dev/null +++ b/src/views/edit/PlateContainer.tsx @@ -0,0 +1,367 @@ +import React from "react"; +import { withProps } from "@udecode/cn"; +import { observer } from "mobx-react-lite"; +import { Node as SNode } from "slate"; +import { + Plate, + PlateContent, + RenderAfterEditable, + createPlugins, + createReactPlugin, + createHistoryPlugin, + isBlockAboveEmpty, + isSelectionAtBlockStart, + PlateLeaf, + PlateElement, +} from "@udecode/plate-common"; + +import { + createBasicElementsPlugin, + createBasicMarksPlugin, + createIndentListPlugin, + createIndentPlugin, + + // @udecode/plate-autoformat + createAutoformatPlugin, + + // imported for resetNodePlugin + unwrapCodeBlock, + isSelectionAtCodeBlockStart, + isCodeBlockEmpty, + + // elements + KEYS_HEADING, + ELEMENT_H1, + ELEMENT_H2, + ELEMENT_H3, + ELEMENT_H4, + ELEMENT_H5, + ELEMENT_H6, + ELEMENT_BLOCKQUOTE, + ELEMENT_CODE_BLOCK, + ELEMENT_CODE_LINE, // inside code_block, not inline code (that's mark_code) + ELEMENT_CODE_SYNTAX, + ELEMENT_LINK, + ELEMENT_LI, + ELEMENT_TD, + ELEMENT_TODO_LI, + ELEMENT_UL, + ELEMENT_OL, + MARK_BOLD, + MARK_CODE, + MARK_ITALIC, + MARK_STRIKETHROUGH, + MARK_SUBSCRIPT, + MARK_SUPERSCRIPT, + MARK_UNDERLINE, + + // images + // https://platejs.org/docs/media + createSelectOnBackspacePlugin, + ELEMENT_IMAGE, + createImagePlugin, + ELEMENT_MEDIA_EMBED, + + // createTogglePlugin + + // So document always has a trailing paragraph, ensures you + // can always type after the last non-paragraph block. + createTrailingBlockPlugin, + ELEMENT_PARAGRAPH, + + // links + createLinkPlugin, + createSoftBreakPlugin, + createExitBreakPlugin, + createResetNodePlugin, + createListPlugin, +} from "@udecode/plate"; + +import { createCaptionPlugin } from "@udecode/plate-caption"; + +import { + BlockquoteElement, + CodeBlockElement, + CodeLeaf, + CodeSyntaxLeaf, + CodeLineElement, + HeadingElement, + ParagraphElement, + ImageElement, + LinkElement, + ListElement, + LinkFloatingToolbar, + VideoElement, +} from "./editor/elements"; + +import { autoformatRules } from "./editor/plugins/autoformat/autoformatRules"; +import { createCodeBlockNormalizationPlugin } from "./editor/plugins/createCodeBlockNormalizationPlugin"; + +import { + ELEMENT_VIDEO, + createVideoPlugin, +} from "./editor/plugins/createVideoPlugin"; +import { createFilesPlugin } from "./editor/plugins/createFilesPlugin"; + +// Ideally this is injected; also createVideoPlugin and createFilesPlugin do this +import { IClient } from "../../preload/client/types"; +const client: IClient = (window as any).chronicles.createClient(); + +import { EditorToolbar } from "./editor/toolbar/EditorToolbar"; +import { EditorMode } from "./EditorMode"; +import FrontMatter from "./FrontMatter"; +import { Pane } from "evergreen-ui"; +import { EditableDocument } from "./EditableDocument"; +import { JournalResponse } from "../../hooks/useClient"; + +export interface Props { + saving: boolean; + value: SNode[]; + document: EditableDocument; + journals: JournalResponse[]; + setValue: (n: SNode[]) => any; + selectedEditorMode: EditorMode; + setSelectedEditorMode: (s: EditorMode) => any; +} + +export default observer( + ({ + children, + document, + journals, + saving, + value, + setValue, + selectedEditorMode, + setSelectedEditorMode, + }: React.PropsWithChildren) => { + // todo: Commented out stuff is post-copy paste edit from plate-ui (as recommended), and should used to guide next steps. + const plugins = createPlugins( + [ + createCodeBlockNormalizationPlugin(), + + // editor + createReactPlugin(), // withReact + createHistoryPlugin(), // withHistory + + // Paragraph, blockquote, code block, heading, etcj + // https://platejs.org/docs/basic-elements + createBasicElementsPlugin(), + + // marks: bold, iatlic, underline, etc + createBasicMarksPlugin(), + + createLinkPlugin({ + // Without the toolbar, links cannot be easily edited + renderAfterEditable: LinkFloatingToolbar as RenderAfterEditable, + options: { + allowedSchemes: ["http", "https", "mailto", "chronicles"], + }, + }), + + createListPlugin(), + + // https://platejs.org/docs/media + createCaptionPlugin({ + options: { pluginKeys: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] }, + }), + createImagePlugin({ + options: { + uploadImage: client.files.uploadImage, + }, + }), + + // Plate's media handler turns youtube links, twitter links, etc, into embeds. + // I'm unsure how to trigger the logic, probably via toolbar or shortcut. + // createMediaEmbedPlugin(), + + // NOTE: These plugins MUST come after createImagePlugin, otherwise createImagePlugin swallows + // dropped video files and this won't be called. + createVideoPlugin(), + createFilesPlugin(), + + // Backspacing into an element selects the block before deleting it. + createSelectOnBackspacePlugin({ + options: { + query: { + allow: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED], + }, + }, + }), + + // createTodoListPlugin(), + + // So you can shift+enter new-line inside of the specified elements + // https://platejs.org/docs/soft-break + createSoftBreakPlugin({ + options: { + rules: [ + { hotkey: "shift+enter" }, + { + hotkey: "enter", + query: { + allow: [ELEMENT_CODE_BLOCK, ELEMENT_BLOCKQUOTE, ELEMENT_TD], + }, + }, + ], + }, + }), + + // Exit text blocks with cmd+enter + // https://platejs.org/docs/exit-break + createExitBreakPlugin({ + options: { + rules: [ + { + hotkey: "mod+enter", + }, + { + hotkey: "mod+shift+enter", + before: true, + }, + { + hotkey: "enter", + query: { + start: true, + end: true, + allow: KEYS_HEADING, // shrug emoji + }, + relative: true, + level: 1, + }, + ], + }, + }), + + // Reset block when hitting enter, e.g. to stop + // making new todo list items, etc. + // https://platejs.org/docs/reset-node + createResetNodePlugin({ + options: { + rules: [ + { + types: [ELEMENT_BLOCKQUOTE, ELEMENT_TODO_LI], + defaultType: ELEMENT_PARAGRAPH, + hotkey: "Enter", + predicate: isBlockAboveEmpty, + }, + { + types: [ELEMENT_BLOCKQUOTE, ELEMENT_TODO_LI], + defaultType: ELEMENT_PARAGRAPH, + hotkey: "Backspace", + predicate: isSelectionAtBlockStart, + }, + { + types: [ELEMENT_CODE_BLOCK], + defaultType: ELEMENT_PARAGRAPH, + onReset: unwrapCodeBlock, + hotkey: "Enter", + predicate: isCodeBlockEmpty, + }, + { + types: [ELEMENT_CODE_BLOCK], + defaultType: ELEMENT_PARAGRAPH, + onReset: unwrapCodeBlock, + hotkey: "Backspace", + predicate: isSelectionAtCodeBlockStart, + }, + ], + }, + }), + + // Set text block indentation for differentiating structural + // elements or emphasizing certain content sections. + // https://platejs.org/docs/indent + createIndentPlugin({ + inject: { + props: { + validTypes: [ + // These elements from my prior implementation; docs + // only have paragraph and h1 + ELEMENT_PARAGRAPH, + ELEMENT_H1, + ELEMENT_H2, + ELEMENT_H3, + ELEMENT_H4, + ELEMENT_H5, + ELEMENT_H6, + ELEMENT_BLOCKQUOTE, + ELEMENT_CODE_BLOCK, + ], + }, + }, + }), + + createIndentListPlugin(), + + // Ensures there is always a paragraph element at the end of the document; avoids + // document getting stuck with an image or other non-text element at the end, or + // being confused about how to exit an e.g. code block to add more content. + createTrailingBlockPlugin({ type: ELEMENT_PARAGRAPH }), + + // e.g. # -> h1, ``` -> code block, etc + createAutoformatPlugin({ + options: { + rules: autoformatRules, + enableUndoOnDelete: true, + }, + }), + ], + { + components: { + [ELEMENT_VIDEO]: VideoElement, + [ELEMENT_BLOCKQUOTE]: BlockquoteElement, + [ELEMENT_CODE_BLOCK]: CodeBlockElement, + [ELEMENT_CODE_LINE]: CodeLineElement, + [ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf, + [MARK_CODE]: CodeLeaf, + // [ELEMENT_HR]: HrElement, + [ELEMENT_H1]: withProps(HeadingElement, { variant: "h1" }), + [ELEMENT_H2]: withProps(HeadingElement, { variant: "h2" }), + [ELEMENT_H3]: withProps(HeadingElement, { variant: "h3" }), + [ELEMENT_H4]: withProps(HeadingElement, { variant: "h4" }), + [ELEMENT_H5]: withProps(HeadingElement, { variant: "h5" }), + [ELEMENT_H6]: withProps(HeadingElement, { variant: "h6" }), + [ELEMENT_IMAGE]: ImageElement, + [ELEMENT_LINK]: LinkElement, + // todo: need more plugins to make these truly usable. + // [ELEMENT_MEDIA_EMBED]: MediaEmbedElement, + // [ELEMENT_MENTION]: MentionElement, + // [ELEMENT_MENTION_INPUT]: MentionInputElement, + [ELEMENT_UL]: withProps(ListElement, { variant: "ul" }), + [ELEMENT_LI]: withProps(PlateElement, { as: "li" }), + [ELEMENT_OL]: withProps(ListElement, { variant: "ol" }), + [ELEMENT_PARAGRAPH]: ParagraphElement, + // [ELEMENT_TABLE]: TableElement, + // [ELEMENT_TD]: TableCellElement, + // [ELEMENT_TH]: TableCellHeaderElement, + // [ELEMENT_TODO_LI]: TodoListElement, + // [ELEMENT_TR]: TableRowElement, + // [ELEMENT_EXCALIDRAW]: ExcalidrawElement, + [MARK_BOLD]: withProps(PlateLeaf, { as: "strong" }), + + // Unsure about these: + // [MARK_HIGHLIGHT]: HighlightLeaf, + [MARK_ITALIC]: withProps(PlateLeaf, { as: "em" }), + // [MARK_KBD]: KbdLeaf, + [MARK_STRIKETHROUGH]: withProps(PlateLeaf, { as: "s" }), + [MARK_SUBSCRIPT]: withProps(PlateLeaf, { as: "sub" }), + [MARK_SUPERSCRIPT]: withProps(PlateLeaf, { as: "sup" }), + [MARK_UNDERLINE]: withProps(PlateLeaf, { as: "u" }), + // [MARK_COMMENT]: CommentLeaf, + }, + }, + ); + + return ( + + {children} + + ); + }, +); diff --git a/src/views/edit/editor/components/Toolbar.tsx b/src/views/edit/editor/components/Toolbar.tsx index 5830ead..d481771 100644 --- a/src/views/edit/editor/components/Toolbar.tsx +++ b/src/views/edit/editor/components/Toolbar.tsx @@ -10,7 +10,7 @@ import { withTooltip } from "./Tooltip"; export const Toolbar = withCn( ToolbarPrimitive.Root, - "relative flex select-none items-center gap-1 bg-background", + "relative flex select-none items-center gap-1", ); export const ToolbarToggleGroup = withCn( @@ -30,21 +30,24 @@ export const ToolbarSeparator = withCn( const toolbarButtonVariants = cva( cn( - "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center text-xs ring-offset-background transition-colors disabled:pointer-events-none disabled:opacity-50", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "[&_svg:not([data-icon])]:h-5 [&_svg:not([data-icon])]:w-5", + "rounded-sm border border-transparent", ), { variants: { variant: { default: - "bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground", + "hover:bg-slate-50 hover:border-slate-400 hover:border hover:text-accent-foreground aria-checked:text-accent-foreground ", //border-1 border-transparent bg-transparent outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", }, size: { - default: "h-10 px-3", - sm: "h-9 px-2", - lg: "h-11 px-5", + default: "h-7 p-1.5", + xs: "h-5 p-1.5", + sm: "h-7 p-1.5", + lg: "h-9 p-1.5", }, }, defaultVariants: { @@ -54,8 +57,10 @@ const toolbarButtonVariants = cva( }, ); -// todo: fix any -const ToolbarButton: any = withTooltip( +/** + * A styled button for the toolbar. + */ +const ToolbarButton = withTooltip( // eslint-disable-next-line react/display-name React.forwardRef< React.ElementRef, @@ -85,22 +90,12 @@ const ToolbarButton: any = withTooltip( variant, size, }), - isDropdown && "my-1 justify-between pr-1", className, )} value={pressed ? "single" : ""} {...props} > - {isDropdown ? ( - <> -
{children}
-
- -
- - ) : ( - children - )} + {children} ) : ( @@ -122,7 +117,9 @@ const ToolbarButton: any = withTooltip( }, ), ); + ToolbarButton.displayName = "ToolbarButton"; + export { ToolbarButton }; export const ToolbarToggleItem = withVariants( @@ -148,13 +145,7 @@ export const ToolbarGroup = withRef< )} -
{children}
+
{children}
); }); - -// Also https://platejs.org/docs/components/toolbar -export const FixedToolbar = withCn( - Toolbar, - "supports-backdrop-blur:bg-background/60 sticky left-0 top-[57px] z-50 w-full overflow-x-auto rounded-t-lg border-b border-b-border bg-background/95 backdrop-blur", -); diff --git a/src/views/edit/editor/index.tsx b/src/views/edit/editor/index.tsx index 8d4a738..4fe38c3 100644 --- a/src/views/edit/editor/index.tsx +++ b/src/views/edit/editor/index.tsx @@ -1,359 +1,6 @@ import React from "react"; -import { withProps } from "@udecode/cn"; -import { observer } from "mobx-react-lite"; -import { Node as SNode } from "slate"; -import { - Plate, - PlateContent, - RenderAfterEditable, - createPlugins, - createReactPlugin, - createHistoryPlugin, - isBlockAboveEmpty, - isSelectionAtBlockStart, - PlateLeaf, - PlateElement, -} from "@udecode/plate-common"; +import { PlateContent } from "@udecode/plate-common"; -import { - createBasicElementsPlugin, - createBasicMarksPlugin, - createIndentListPlugin, - createIndentPlugin, - - // @udecode/plate-autoformat - createAutoformatPlugin, - - // imported for resetNodePlugin - unwrapCodeBlock, - isSelectionAtCodeBlockStart, - isCodeBlockEmpty, - - // elements - KEYS_HEADING, - ELEMENT_H1, - ELEMENT_H2, - ELEMENT_H3, - ELEMENT_H4, - ELEMENT_H5, - ELEMENT_H6, - ELEMENT_BLOCKQUOTE, - ELEMENT_CODE_BLOCK, - ELEMENT_CODE_LINE, // inside code_block, not inline code (that's mark_code) - ELEMENT_CODE_SYNTAX, - ELEMENT_LINK, - ELEMENT_LI, - ELEMENT_TD, - ELEMENT_TODO_LI, - ELEMENT_UL, - ELEMENT_OL, - MARK_BOLD, - MARK_CODE, - MARK_ITALIC, - MARK_STRIKETHROUGH, - MARK_SUBSCRIPT, - MARK_SUPERSCRIPT, - MARK_UNDERLINE, - - // images - // https://platejs.org/docs/media - createSelectOnBackspacePlugin, - ELEMENT_IMAGE, - createImagePlugin, - ELEMENT_MEDIA_EMBED, - - // createTogglePlugin - - // So document always has a trailing paragraph, ensures you - // can always type after the last non-paragraph block. - createTrailingBlockPlugin, - ELEMENT_PARAGRAPH, - - // links - createLinkPlugin, - createSoftBreakPlugin, - createExitBreakPlugin, - createResetNodePlugin, - createListPlugin, -} from "@udecode/plate"; - -import { createCaptionPlugin } from "@udecode/plate-caption"; - -import { - BlockquoteElement, - CodeBlockElement, - CodeLeaf, - CodeSyntaxLeaf, - CodeLineElement, - HeadingElement, - ParagraphElement, - ImageElement, - LinkElement, - ListElement, - LinkFloatingToolbar, - VideoElement, -} from "./elements"; - -import { autoformatRules } from "./plugins/autoformat/autoformatRules"; -import { createCodeBlockNormalizationPlugin } from "./plugins/createCodeBlockNormalizationPlugin"; - -import { ELEMENT_VIDEO, createVideoPlugin } from "./plugins/createVideoPlugin"; -import { createFilesPlugin } from "./plugins/createFilesPlugin"; - -// Ideally this is injected; also createVideoPlugin and createFilesPlugin do this -import { IClient } from "../../../preload/client/types"; -const client: IClient = (window as any).chronicles.createClient(); - -import { EditorToolbar } from "./toolbar/EditorToolbar"; -import { EditorMode } from "../EditorMode"; - -export interface Props { - saving: boolean; - value: SNode[]; - setValue: (n: SNode[]) => any; - selectedEditorMode: EditorMode; - setSelectedEditorMode: (s: EditorMode) => any; +export default function () { + return ; } - -export default observer( - ({ - saving, - value, - setValue, - selectedEditorMode, - setSelectedEditorMode, - }: Props) => { - // todo: Commented out stuff is post-copy paste edit from plate-ui (as recommended), and should used to guide next steps. - const plugins = createPlugins( - [ - createCodeBlockNormalizationPlugin(), - - // editor - createReactPlugin(), // withReact - createHistoryPlugin(), // withHistory - - // Paragraph, blockquote, code block, heading, etcj - // https://platejs.org/docs/basic-elements - createBasicElementsPlugin(), - - // marks: bold, iatlic, underline, etc - createBasicMarksPlugin(), - - createLinkPlugin({ - // Without the toolbar, links cannot be easily edited - renderAfterEditable: LinkFloatingToolbar as RenderAfterEditable, - options: { - allowedSchemes: ["http", "https", "mailto", "chronicles"], - }, - }), - - createListPlugin(), - - // https://platejs.org/docs/media - createCaptionPlugin({ - options: { pluginKeys: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] }, - }), - createImagePlugin({ - options: { - uploadImage: client.files.uploadImage, - }, - }), - - // Plate's media handler turns youtube links, twitter links, etc, into embeds. - // I'm unsure how to trigger the logic, probably via toolbar or shortcut. - // createMediaEmbedPlugin(), - - // NOTE: These plugins MUST come after createImagePlugin, otherwise createImagePlugin swallows - // dropped video files and this won't be called. - createVideoPlugin(), - createFilesPlugin(), - - // Backspacing into an element selects the block before deleting it. - createSelectOnBackspacePlugin({ - options: { - query: { - allow: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED], - }, - }, - }), - - // createTodoListPlugin(), - - // So you can shift+enter new-line inside of the specified elements - // https://platejs.org/docs/soft-break - createSoftBreakPlugin({ - options: { - rules: [ - { hotkey: "shift+enter" }, - { - hotkey: "enter", - query: { - allow: [ELEMENT_CODE_BLOCK, ELEMENT_BLOCKQUOTE, ELEMENT_TD], - }, - }, - ], - }, - }), - - // Exit text blocks with cmd+enter - // https://platejs.org/docs/exit-break - createExitBreakPlugin({ - options: { - rules: [ - { - hotkey: "mod+enter", - }, - { - hotkey: "mod+shift+enter", - before: true, - }, - { - hotkey: "enter", - query: { - start: true, - end: true, - allow: KEYS_HEADING, // shrug emoji - }, - relative: true, - level: 1, - }, - ], - }, - }), - - // Reset block when hitting enter, e.g. to stop - // making new todo list items, etc. - // https://platejs.org/docs/reset-node - createResetNodePlugin({ - options: { - rules: [ - { - types: [ELEMENT_BLOCKQUOTE, ELEMENT_TODO_LI], - defaultType: ELEMENT_PARAGRAPH, - hotkey: "Enter", - predicate: isBlockAboveEmpty, - }, - { - types: [ELEMENT_BLOCKQUOTE, ELEMENT_TODO_LI], - defaultType: ELEMENT_PARAGRAPH, - hotkey: "Backspace", - predicate: isSelectionAtBlockStart, - }, - { - types: [ELEMENT_CODE_BLOCK], - defaultType: ELEMENT_PARAGRAPH, - onReset: unwrapCodeBlock, - hotkey: "Enter", - predicate: isCodeBlockEmpty, - }, - { - types: [ELEMENT_CODE_BLOCK], - defaultType: ELEMENT_PARAGRAPH, - onReset: unwrapCodeBlock, - hotkey: "Backspace", - predicate: isSelectionAtCodeBlockStart, - }, - ], - }, - }), - - // Set text block indentation for differentiating structural - // elements or emphasizing certain content sections. - // https://platejs.org/docs/indent - createIndentPlugin({ - inject: { - props: { - validTypes: [ - // These elements from my prior implementation; docs - // only have paragraph and h1 - ELEMENT_PARAGRAPH, - ELEMENT_H1, - ELEMENT_H2, - ELEMENT_H3, - ELEMENT_H4, - ELEMENT_H5, - ELEMENT_H6, - ELEMENT_BLOCKQUOTE, - ELEMENT_CODE_BLOCK, - ], - }, - }, - }), - - createIndentListPlugin(), - - // Ensures there is always a paragraph element at the end of the document; avoids - // document getting stuck with an image or other non-text element at the end, or - // being confused about how to exit an e.g. code block to add more content. - createTrailingBlockPlugin({ type: ELEMENT_PARAGRAPH }), - - // e.g. # -> h1, ``` -> code block, etc - createAutoformatPlugin({ - options: { - rules: autoformatRules, - enableUndoOnDelete: true, - }, - }), - ], - { - components: { - [ELEMENT_VIDEO]: VideoElement, - [ELEMENT_BLOCKQUOTE]: BlockquoteElement, - [ELEMENT_CODE_BLOCK]: CodeBlockElement, - [ELEMENT_CODE_LINE]: CodeLineElement, - [ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf, - [MARK_CODE]: CodeLeaf, - // [ELEMENT_HR]: HrElement, - [ELEMENT_H1]: withProps(HeadingElement, { variant: "h1" }), - [ELEMENT_H2]: withProps(HeadingElement, { variant: "h2" }), - [ELEMENT_H3]: withProps(HeadingElement, { variant: "h3" }), - [ELEMENT_H4]: withProps(HeadingElement, { variant: "h4" }), - [ELEMENT_H5]: withProps(HeadingElement, { variant: "h5" }), - [ELEMENT_H6]: withProps(HeadingElement, { variant: "h6" }), - [ELEMENT_IMAGE]: ImageElement, - [ELEMENT_LINK]: LinkElement, - // todo: need more plugins to make these truly usable. - // [ELEMENT_MEDIA_EMBED]: MediaEmbedElement, - // [ELEMENT_MENTION]: MentionElement, - // [ELEMENT_MENTION_INPUT]: MentionInputElement, - [ELEMENT_UL]: withProps(ListElement, { variant: "ul" }), - [ELEMENT_LI]: withProps(PlateElement, { as: "li" }), - [ELEMENT_OL]: withProps(ListElement, { variant: "ol" }), - [ELEMENT_PARAGRAPH]: ParagraphElement, - // [ELEMENT_TABLE]: TableElement, - // [ELEMENT_TD]: TableCellElement, - // [ELEMENT_TH]: TableCellHeaderElement, - // [ELEMENT_TODO_LI]: TodoListElement, - // [ELEMENT_TR]: TableRowElement, - // [ELEMENT_EXCALIDRAW]: ExcalidrawElement, - [MARK_BOLD]: withProps(PlateLeaf, { as: "strong" }), - - // Unsure about these: - // [MARK_HIGHLIGHT]: HighlightLeaf, - [MARK_ITALIC]: withProps(PlateLeaf, { as: "em" }), - // [MARK_KBD]: KbdLeaf, - [MARK_STRIKETHROUGH]: withProps(PlateLeaf, { as: "s" }), - [MARK_SUBSCRIPT]: withProps(PlateLeaf, { as: "sub" }), - [MARK_SUPERSCRIPT]: withProps(PlateLeaf, { as: "sup" }), - [MARK_UNDERLINE]: withProps(PlateLeaf, { as: "u" }), - // [MARK_COMMENT]: CommentLeaf, - }, - }, - ); - - return ( - - - - - ); - }, -); diff --git a/src/views/edit/editor/read-only-editor/ReadonlyToolbar.tsx b/src/views/edit/editor/read-only-editor/ReadonlyToolbar.tsx index e2ce371..ed7120b 100644 --- a/src/views/edit/editor/read-only-editor/ReadonlyToolbar.tsx +++ b/src/views/edit/editor/read-only-editor/ReadonlyToolbar.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { ToolbarGroup, FixedToolbar } from "../components/Toolbar"; +import { ToolbarGroup, Toolbar } from "../components/Toolbar"; import { TooltipProvider } from "../components/Tooltip"; import DebugDropdown from "../toolbar/components/DebugDropdown"; import { EditorMode } from "../../EditorMode"; @@ -36,14 +36,15 @@ export function ReadonlyToolbar({ >
<> - + {}} /> - +
diff --git a/src/views/edit/editor/toolbar/EditorToolbar.tsx b/src/views/edit/editor/toolbar/EditorToolbar.tsx index d69027f..fa67ab0 100644 --- a/src/views/edit/editor/toolbar/EditorToolbar.tsx +++ b/src/views/edit/editor/toolbar/EditorToolbar.tsx @@ -13,17 +13,32 @@ import { Icons } from "../../../../components/icons"; import { LinkToolbarButton } from "./components/LinkToolbarButton"; import { MarkToolbarButton } from "./components/MarkToolbarButton"; -import { ToolbarGroup, FixedToolbar } from "../components/Toolbar"; +import { ToolbarGroup, Toolbar } from "../components/Toolbar"; // I think this needs to be on the root, wherever Theme would be added (if we had a theme) import { TooltipProvider } from "../components/Tooltip"; import ChangeBlockDropdown from "./components/ChangeBlockDropdown"; import DebugDropdown from "./components/DebugDropdown"; import { EditorMode } from "../../EditorMode"; +import { + BoldIcon, + ItalicIcon, + UnderlineIcon, + StrikethroughIcon, + CodeIcon, + LinkIcon, + MoreIcon, +} from "evergreen-ui"; +import { Underline } from "lucide-react"; +import { EditableDocument } from "../../EditableDocument"; +import { useIsMounted } from "../../../../hooks/useIsMounted"; +import { useNavigate } from "react-router-dom"; +import { useSearchStore } from "../../../documents/SearchStore"; interface Props { selectedEditorMode: EditorMode; setSelectedEditorMode: (s: EditorMode) => any; + document: EditableDocument; } /** @@ -34,40 +49,53 @@ interface Props { export function EditorToolbar({ selectedEditorMode, setSelectedEditorMode, + document, }: Props) { + const isMounted = useIsMounted(); + const navigate = useNavigate(); + const searchStore = useSearchStore()!; + + async function deleteDocument() { + if (confirm("Are you sure?")) { + await document.del(); + searchStore.updateSearch(document, "del"); + if (isMounted()) navigate(-1); + } + } + return ( - {/* NOTE: Unclear why I cant use Tailwind class mb-24 here */} -
+
+
<> - - + + - + - + - + <> @@ -75,41 +103,24 @@ export function EditorToolbar({ tooltip="Strikethrough (⌘+⇧+M)" nodeType={MARK_STRIKETHROUGH} > - + - + - - {/* NOTE: id prop was removed, probably was for Playground to support multiple editors */} - {/* */} - {/* {isEnabled("indentlist", id) && indentList && ( - <> - - - - )} */} - - {/* NOTE: id prop was removed, probably was for Playground to support multiple editors */} - {/* <> - - - */} - - + - + - -
diff --git a/src/views/edit/editor/toolbar/components/ChangeBlockDropdown.tsx b/src/views/edit/editor/toolbar/components/ChangeBlockDropdown.tsx index 8260cd2..da21c48 100644 --- a/src/views/edit/editor/toolbar/components/ChangeBlockDropdown.tsx +++ b/src/views/edit/editor/toolbar/components/ChangeBlockDropdown.tsx @@ -24,6 +24,21 @@ import { import { Icons } from "../../../../../components/icons"; +import { + MoreIcon, + HeaderOneIcon, + HeaderTwoIcon, + HeaderThreeIcon, + CodeBlockIcon, + CitationIcon, + NumberedListIcon, + PropertiesIcon, + FontIcon, + DeleteIcon, + TrashIcon, + ParagraphIcon, +} from "evergreen-ui"; + import { DropdownMenu, DropdownMenuContent, @@ -38,51 +53,51 @@ import { ToolbarButton } from "../../components/Toolbar"; const items = [ { description: "Paragraph", - icon: Icons.paragraph, + icon: ParagraphIcon, label: "Paragraph", value: ELEMENT_PARAGRAPH, }, { description: "Heading 1", - icon: Icons.h1, + icon: HeaderOneIcon, label: "Heading 1", value: ELEMENT_H1, }, { description: "Heading 2", - icon: Icons.h2, + icon: HeaderTwoIcon, label: "Heading 2", value: ELEMENT_H2, }, { description: "Heading 3", - icon: Icons.h3, + icon: HeaderThreeIcon, label: "Heading 3", value: ELEMENT_H3, }, { description: "Quote (⌘+⇧+.)", - icon: Icons.blockquote, + icon: CitationIcon, label: "Quote", value: ELEMENT_BLOCKQUOTE, }, { description: "Code (```)", - icon: Icons.codeblock, + icon: CodeBlockIcon, label: "Code", value: ELEMENT_CODE_BLOCK, }, { - value: "ul", - label: "Bulleted list", description: "Bulleted list", - icon: Icons.ul, + icon: PropertiesIcon, + label: "Bulleted list", + value: "ul", }, { - value: "ol", - label: "Numbered list", description: "Numbered list", - icon: Icons.ol, + icon: NumberedListIcon, + label: "Numbered list", + value: "ol", }, ]; @@ -91,9 +106,6 @@ const defaultItem = items.find((item) => item.value === ELEMENT_PARAGRAPH)!; /** * Toolbar dropdown for toggling block types, e.g. paragraph, heading, blockquote. * - * Unlike the InsertBlockDropdown, this dropdown is for toggling existing blocks, not - * creating new ones. - * * Referred to as "Turn into" dropdown in Plate UI docs. * https://platejs.org/docs/components/turn-into-dropdown-menu */ @@ -129,7 +141,7 @@ export default function ChangeBlockDropdown(props: DropdownMenuProps) { className="lg:min-w-[130px]" isDropdown pressed={openState.open} - tooltip="Turn into" + tooltip="Change type" > {selectedItemLabel} diff --git a/src/views/edit/editor/toolbar/components/DebugDropdown.tsx b/src/views/edit/editor/toolbar/components/DebugDropdown.tsx index 16164dc..c77adbb 100644 --- a/src/views/edit/editor/toolbar/components/DebugDropdown.tsx +++ b/src/views/edit/editor/toolbar/components/DebugDropdown.tsx @@ -4,12 +4,29 @@ import { ToolbarButton } from "../../components/Toolbar"; import { useOpenState, DropdownMenu, + DropdownMenuLabel, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, } from "./DropdownMenu"; -import { Icons } from "../../../../../components/icons"; + +import { + MoreIcon, + HeaderOneIcon, + HeaderTwoIcon, + HeaderThreeIcon, + CodeBlockIcon, + CitationIcon, + NumberedListIcon, + PropertiesIcon, + FontIcon, + DeleteIcon, + TrashIcon, +} from "evergreen-ui"; + import { EditorMode } from "../../../EditorMode"; const options = Object.freeze([ @@ -22,6 +39,7 @@ const options = Object.freeze([ interface Props { selectedEditorMode: EditorMode; setSelectedEditorMode: (s: EditorMode) => any; + deleteDocument: () => void; } /** @@ -30,6 +48,7 @@ interface Props { export default function DebugDropdown({ selectedEditorMode, setSelectedEditorMode, + deleteDocument, }: Props) { const openState = useOpenState(); @@ -40,29 +59,38 @@ export default function DebugDropdown({ isDropdown pressed={openState.open} tooltip="Change editor debug mode" + size="xs" > - + - + + Toggle debug mode + + {options.map(({ key, label }) => ( + + { + setSelectedEditorMode(key); + }} + > + {label} + + + ))} + + - {options.map(({ key, label }) => ( - - { - setSelectedEditorMode(key); - }} - > - {label} - - - ))} + + + ); diff --git a/src/views/edit/editor/toolbar/components/DropdownMenu.tsx b/src/views/edit/editor/toolbar/components/DropdownMenu.tsx index 3e0a00f..59fa373 100644 --- a/src/views/edit/editor/toolbar/components/DropdownMenu.tsx +++ b/src/views/edit/editor/toolbar/components/DropdownMenu.tsx @@ -56,12 +56,12 @@ export const DropdownMenuSubTrigger = withRef< export const DropdownMenuSubContent = withCn( DropdownMenuPrimitive.SubContent, - "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 min-w-32 overflow-hidden border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", ); const DropdownMenuContentVariants = withProps(DropdownMenuPrimitive.Content, { className: cn( - "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 min-w-32 overflow-hidden border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", ), sideOffset: 4, }); @@ -76,7 +76,7 @@ export const DropdownMenuContent = withRef< const menuItemVariants = cva( cn( - "relative flex h-9 cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors", + "relative flex h-9 cursor-pointer select-none items-center px-2 py-1.5 text-sm outline-none transition-colors", "focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", ), { @@ -99,7 +99,7 @@ export const DropdownMenuCheckboxItem = withRef< >(({ children, className, ...props }, ref) => ( (({ children, className, hideIcon, ...props }, ref) => ( ((rest, ref) => { const state = useLinkToolbarButtonState(); @@ -14,7 +14,7 @@ export const LinkToolbarButton = withRef((rest, ref) => { return ( - + ); }); diff --git a/src/views/edit/index.tsx b/src/views/edit/index.tsx index 02e49b7..640c856 100644 --- a/src/views/edit/index.tsx +++ b/src/views/edit/index.tsx @@ -1,21 +1,22 @@ import React, { useContext, useState, useEffect } from "react"; import { observer } from "mobx-react-lite"; import Editor from "./editor"; -import { Pane, Button, Popover, Menu, Position, TagInput } from "evergreen-ui"; +import { IconButton, Pane, ChevronLeftIcon } from "evergreen-ui"; import { useEditableDocument } from "./useEditableDocument"; import { EditableDocument } from "./EditableDocument"; -import { css } from "emotion"; import { JournalResponse } from "../../preload/client/journals"; import { EditLoadingComponent } from "./loading"; -import { DayPicker } from "react-day-picker"; -import { useIsMounted } from "../../hooks/useIsMounted"; import { JournalsStoreContext } from "../../hooks/useJournalsLoader"; import { useParams, useNavigate } from "react-router-dom"; import { useSearchStore } from "../documents/SearchStore"; import ReadOnlyTextEditor from "./editor/read-only-editor/ReadOnlyTextEditor"; import { EditorMode } from "./EditorMode"; -import { TagTokenParser } from "../documents/search/parsers/tag"; -import { Icons } from "../../components/icons"; +import Titlebar from "../../titlebar/macos"; +import PlateContainer from "./PlateContainer"; +import { EditorToolbar } from "./editor/toolbar/EditorToolbar"; +import FrontMatter from "./FrontMatter"; +import { Separator } from "./editor/components/Separator"; +import * as Base from "../layout"; // Loads document, with loading and error placeholders function DocumentLoadingContainer() { @@ -66,181 +67,16 @@ interface DocumentEditProps { */ const DocumentEditView = observer((props: DocumentEditProps) => { const { document, journals } = props; - const isMounted = useIsMounted(); - const navigate = useNavigate(); - const searchStore = useSearchStore()!; const [selectedViewMode, setSelectedViewMode] = React.useState( EditorMode.Editor, ); + const navigate = useNavigate(); + const searchStore = useSearchStore()!; - // Autofocus the heading input - const onInputRendered = React.useCallback( - (inputElement: HTMLInputElement) => { - if (inputElement) { - // After experimenting, unsure why the delay is helpful. - // https://blog.maisie.ink/react-ref-autofocus/ - setTimeout(() => inputElement.focus(), 200); - inputElement.focus(); - } - }, - [], - ); - - // todo: move this to view model - function getName(journalId?: string) { - const journal = journals?.find((j) => j.id === journalId); - return journal ? journal.name : "Unknown journal"; - } - - function makeOptions(close: any) { - return journals.map((j: any) => { - return ( - { - document.journalId = j.id; - close(); - }} - > - {j.name} - - ); - }); - } - - function journalPicker() { - return ( - ( -
- - {makeOptions(close)} - -
- )} - > - - {getName(document.journalId)} - -
- ); - } - - function onDayPick(day: Date, callback: () => void) { - document.createdAt = day.toISOString(); - callback(); - } - - // tests: when changing date, documents date is highlighted - // when changing date, currently selected date's month is the active one - // document auto-saves when changing date - function datePicker() { - return ( - ( -
- onDayPick(day, close)} - mode="single" - /> -
- )} - > - - {document.createdAt.slice(0, 10)} - -
- ); - } - - function goBack() { - if ( - !document.dirty || - confirm( - "Document is unsaved, exiting will discard document. Stop editing anyways?", - ) - ) { - // This handles the edit case but hmm... if its new... it should be added to the search... - // but in what order? Well... if we aren't paginated... it should be at the top. - searchStore.updateSearch(document); - navigate(-1); - } - } - - async function deleteDocument() { - if (confirm("Are you sure?")) { - await document.del(); - searchStore.updateSearch(document, "del"); - if (isMounted()) navigate(-1); - } - } - - function onAddTag(tokens: string[]) { - if (tokens.length > 1) { - // https://evergreen.segment.com/components/tag-input - // Documents say this is single value, Type says array - // Testing says array but with only one value... unsure how multiple - // values end up in the array. - console.warn( - "TagInput.onAdd called with > 1 token? ", - tokens, - "ignoring extra tokens", - ); - } - - let tag = new TagTokenParser().parse(tokens[0])?.value; - if (!tag) return; - - if (!document.tags.includes(tag)) { - document.tags.push(tag); - document.save(); - } - } - - function onRemoveTag(tag: string | React.ReactNode, idx: number) { - if (typeof tag !== "string") return; - document.tags = document.tags.filter((t) => t !== tag); - document.save(); - } - - function renderTab(tab: string) { + function renderEditor(tab: string) { switch (tab) { case EditorMode.Editor: - return ( - - - - ); + return ; case EditorMode.SlateDom: return ( { } } - // flexGrows are needed so save / edit buttons are at bottom on both empty documents - // and scrollable documents + function goBack() { + if ( + !document.dirty || + confirm( + "Document is unsaved, exiting will discard document. Stop editing anyways?", + ) + ) { + // This handles the edit case but hmm... if its new... it should be added to the search... + // but in what order? Well... if we aren't paginated... it should be at the top. + searchStore.updateSearch(document); + navigate(-1); + } + } + return ( - // NOTE: width: 100% prevents child code_blocks (and maybe others) from overflowing - - + + + + + Back to documents + + -
- (document.title = e.target.value)} - value={document.title || ""} // OR '' prevents react complaining about uncontrolled component - placeholder="Untitled document" - /> -
-
- {datePicker()} -  in  - {journalPicker()} -
+ +
-
- -
+ {/* This Ghost div is same height as titlebar, so pushes the main content below it -- necessary for the contents scrollbar to make sense */} + + + + - {renderTab(selectedViewMode)} + + {renderEditor(selectedViewMode)} + - {/* Action buttons */} - - - - - + {/* Add padding to bottom of editor without disrupting the scrollbar on the parent */} + +
+ + + ); }); diff --git a/src/views/edit/loading.tsx b/src/views/edit/loading.tsx index a3dab21..3130ac4 100644 --- a/src/views/edit/loading.tsx +++ b/src/views/edit/loading.tsx @@ -1,8 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { Pane, Button, Alert } from "evergreen-ui"; -import { css } from "emotion"; +import { Pane, IconButton, ChevronLeftIcon } from "evergreen-ui"; import { useNavigate } from "react-router-dom"; +import Titlebar from "../../titlebar/macos"; +import { Separator } from "./editor/components/Separator"; export interface LoadingComponentProps { error?: Error | null; @@ -15,68 +16,27 @@ export const EditLoadingComponent = observer((props: LoadingComponentProps) => { const navigate = useNavigate(); return ( - - navigate("/documents")}>Back - -
+ + {}} + marginRight={8} > -
- {placeholderDate}/ -
- - Loading... - -
-
- -
- - {/* note: its not actually clear to me whether toJS is necessary here. */} - - {props.error && ( - - {JSON.stringify(props.error)} - - )} - - - + Back to documents + + + + + + +

Coming soon...

+
-
+ ); }); diff --git a/src/views/layout.tsx b/src/views/layout.tsx new file mode 100644 index 0000000..9c13dc8 --- /dev/null +++ b/src/views/layout.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +/** + * Main content container. + * + * Usage: + * + * ``` + * import * as Base from "../layout"; + * + * + * + * + * + * {...page content here...} + * + * + * ``` + */ +export const Container = ({ children }: React.PropsWithChildren) => { + return ( +
{children}
+ ); +}; + +/** + * Add space equivalent to the fixed position titlebar. See Container for usage. + */ +export const TitlebarSpacer = () => { + return
; +}; + +/** + * Add padding at the bottom of ScrollContainer without disrupting the scrollbar on the parent. + * See Container for usage. + */ +export const BottomSpacer = () => { + return
; +}; + +/** + * Scrollable container for main content. See Container for usage. + */ +export const ScrollContainer = ({ children }: React.PropsWithChildren) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/views/preferences/index.tsx b/src/views/preferences/index.tsx index 962bd27..5b23ca9 100644 --- a/src/views/preferences/index.tsx +++ b/src/views/preferences/index.tsx @@ -1,7 +1,9 @@ import React, { useState, useEffect, PropsWithChildren } from "react"; -import { Pane, Button } from "evergreen-ui"; +import { Pane, Button, IconButton, ChevronLeftIcon } from "evergreen-ui"; import useClient from "../../hooks/useClient"; -import { RouteProps } from "react-router-dom"; +import { RouteProps, useNavigate } from "react-router-dom"; +import * as Base from "../layout"; +import Titlebar from "../../titlebar/macos"; interface Props extends RouteProps { // TODO: any added to satisfy ts check step; previously @@ -13,6 +15,7 @@ export default function Preferences(props: Props) { const [preferences, setPreferences] = useState({}); const [loading, setLoading] = useState(false); const client = useClient(); + const navigate = useNavigate(); // todo: Ideally this could go into a preload script // see the main script (electron/index) for the other half @@ -54,61 +57,78 @@ export default function Preferences(props: Props) { }, []); return ( - - -

Database

-

Chronicles documents are stored in a SQLite database file.

-

This file is located at:

-

- {preferences.DATABASE_URL} -

-
    -
  • - To create a backup: Make a copy of the database file -
  • -
  • - To restore from a backup: Use the filepicker below to point - the app at your backed up file -
  • -
  • - To change the files location: First move the existing - database file to your desired location, then use the - filepicker to select the new location -
  • -  then restart the app -
+ + + navigate(-1)} + marginRight={8} + > + Back to prior page + + + + + +

Database

+

Chronicles documents are stored in a SQLite database file.

+

This file is located at:

+

+ {preferences.DATABASE_URL} +

+
    +
  • + To create a backup: Make a copy of the database file +
  • +
  • + To restore from a backup: Use the filepicker below to point + the app at your backed up file +
  • +
  • + To change the files location: First move the + existing database file to your desired location, then use + the filepicker to select the new location +
  • +  then restart the app +
- {/* todo: https://stackoverflow.com/questions/8579055/how-do-i-move-files-in-node-js/29105404#29105404 */} - -
- -

User Files Directory

-

- Chronicles images and other user files are stored in the `USER_FILES` - directory -

-

This file is located at:

-

- {preferences.USER_FILES_DIR} -

-

- To change the directory location, first move the existing - directory to the desired location, and then select the new location - with th ebutton below -

+ {/* todo: https://stackoverflow.com/questions/8579055/how-do-i-move-files-in-node-js/29105404#29105404 */} + +
+ +

User Files Directory

+

+ Chronicles images and other user files are stored in the + `USER_FILES` directory +

+

This file is located at:

+

+ {preferences.USER_FILES_DIR} +

+

+ To change the directory location, first move the existing + directory to the desired location, and then select the new location + with the button below +

- {/* todo: https://stackoverflow.com/questions/8579055/how-do-i-move-files-in-node-js/29105404#29105404 */} - -
-
+ {/* todo: https://stackoverflow.com/questions/8579055/how-do-i-move-files-in-node-js/29105404#29105404 */} + + + + + + ); } diff --git a/tailwind.config.js b/tailwind.config.js index 11a57d0..93f363f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,8 @@ import { fontFamily } from "tailwindcss/defaultTheme"; /** @type {import('tailwindcss').Config} */ +const plugin = require("tailwindcss/plugin"); + export const darkMode = ["class"]; export const content = ["src/**/*.{ts,tsx}"]; export const theme = { @@ -73,4 +75,21 @@ export const theme = { }, }, }; -export const plugins = [require("tailwindcss-animate")]; + +export const plugins = [ + require("tailwindcss-animate"), + plugin(function ({ addUtilities }) { + addUtilities({ + // The titlebar which replaces the native (default) titlebar is draggable; set .drag-none on + // inner elements that need to be clickable / interactive, like buttons, inputs, etc. + ".drag-none": { + "-webkit-app-region": "no-drag", + // "-webkit-user-drag": "none", + // "-khtml-user-drag": "none", + // "-moz-user-drag": "none", + // "-o-user-drag": "none", + // "user-drag": "none", + }, + }); + }), +]; diff --git a/yarn.lock b/yarn.lock index e041d14..9c87b6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,13 +7,6 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@babel/code-frame@^7.0.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" - integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== - dependencies: - "@babel/highlight" "^7.10.3" - "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" @@ -93,15 +86,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/highlight@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d" - integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -144,13 +128,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.7.2": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" - integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.7.6": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" @@ -337,21 +314,6 @@ fs-extra "^11.1.1" minimist "^1.2.8" -"@emotion/cache@^10.0.27": - version "10.0.29" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" - integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== - dependencies: - "@emotion/sheet" "0.9.4" - "@emotion/stylis" "0.8.5" - "@emotion/utils" "0.11.3" - "@emotion/weak-memoize" "0.2.5" - -"@emotion/hash@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - "@emotion/hash@^0.7.1": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831" @@ -369,42 +331,16 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== -"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": - version "0.11.16" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" - integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg== - dependencies: - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/unitless" "0.7.5" - "@emotion/utils" "0.11.3" - csstype "^2.5.7" - -"@emotion/sheet@0.9.4": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" - integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== - -"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4": +"@emotion/stylis@^0.8.4": version "0.8.5" resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== -"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4": +"@emotion/unitless@^0.7.4": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== -"@emotion/utils@0.11.3": - version "0.11.3" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" - integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== - -"@emotion/weak-memoize@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" - integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== - "@esbuild/aix-ppc64@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" @@ -1430,11 +1366,6 @@ dependencies: undici-types "~5.26.4" -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -2125,31 +2056,6 @@ author-regex@^1.0.0: resolved "https://registry.yarnpkg.com/author-regex/-/author-regex-1.0.0.tgz#d08885be6b9bbf9439fe087c76287245f0a81450" integrity sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g== -babel-plugin-emotion@^10.0.27: - version "10.0.33" - resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz#ce1155dcd1783bbb9286051efee53f4e2be63e03" - integrity sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/serialize" "^0.11.16" - babel-plugin-macros "^2.0.0" - babel-plugin-syntax-jsx "^6.18.0" - convert-source-map "^1.5.0" - escape-string-regexp "^1.0.5" - find-root "^1.1.0" - source-map "^0.5.7" - -babel-plugin-macros@^2.0.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" - integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== - dependencies: - "@babel/runtime" "^7.7.2" - cosmiconfig "^6.0.0" - resolve "^1.12.0" - "babel-plugin-styled-components@>= 1.12.0": version "1.13.2" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.2.tgz#ebe0e6deff51d7f93fceda1819e9b96aeb88278d" @@ -2306,11 +2212,6 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - camelcase-css@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" @@ -2347,7 +2248,7 @@ chai@^5.0.3: loupe "^3.1.0" pathval "^2.0.0" -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2584,34 +2485,6 @@ console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -convert-source-map@^1.5.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - -create-emotion@^10.0.27: - version "10.0.27" - resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-10.0.27.tgz#cb4fa2db750f6ca6f9a001a33fbf1f6c46789503" - integrity sha512-fIK73w82HPPn/RsAij7+Zt8eCE8SptcJ3WoRMfxMtjteYxud8GDTKKld7MYwAX2TVhrw29uR1N/bVGxeStHILg== - dependencies: - "@emotion/cache" "^10.0.27" - "@emotion/serialize" "^0.11.15" - "@emotion/sheet" "0.9.4" - "@emotion/utils" "0.11.3" - cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec" @@ -2657,7 +2530,7 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^2.2.0, csstype@^2.5.7: +csstype@^2.2.0: version "2.6.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== @@ -2888,14 +2761,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emotion@^10.0.27: - version "10.0.27" - resolved "https://registry.yarnpkg.com/emotion/-/emotion-10.0.27.tgz#f9ca5df98630980a23c819a56262560562e5d75e" - integrity sha512-2xdDzdWWzue8R8lu4G76uWX5WhyQuzATon9LmNeCy/2BHVC6dsEpfhN1a0qhELgtDVdjyEA6J8Y/VlI5ZnaH0g== - dependencies: - babel-plugin-emotion "^10.0.27" - create-emotion "^10.0.27" - encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -2925,7 +2790,7 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== @@ -3096,11 +2961,6 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -3565,14 +3425,6 @@ immer@^10.0.3: resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9" integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A== -import-fresh@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3879,11 +3731,6 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" @@ -5278,13 +5125,6 @@ papaparse@^5.4.1: resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127" integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-author@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/parse-author/-/parse-author-2.0.0.tgz#d3460bf1ddd0dfaeed42da754242e65fb684a81f" @@ -5323,16 +5163,6 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - lines-and-columns "^1.1.6" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5353,11 +5183,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -5378,11 +5203,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - pathval@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" @@ -5971,11 +5791,6 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -5990,13 +5805,6 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.22.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.12.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -6069,11 +5877,6 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6257,11 +6060,6 @@ source-map-js@^1.0.2: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - space-separated-tokens@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b" @@ -7125,11 +6923,6 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.7.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - yaml@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"