From 4cb4a34a5ef9f4c3ea0774edd85133f18c99c74e Mon Sep 17 00:00:00 2001 From: K0IN <19688162+K0IN@users.noreply.github.com> Date: Sun, 14 Nov 2021 17:12:39 +0100 Subject: [PATCH 1/8] Added the abillity to save with Ctrl + S * Added Settings entry * Added Editor callback for save status * Added Effect to handle routing and exiting with a warning --- components/container/edit-container.tsx | 18 ++++++++--- components/editor/editor.tsx | 18 ++++++++--- components/settings/explicit-save.tsx | 36 ++++++++++++++++++++++ components/settings/settings-container.tsx | 4 ++- libs/shared/settings.ts | 7 +++++ libs/web/state/ui/useRouterWarning.ts | 31 +++++++++++++++++++ 6 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 components/settings/explicit-save.tsx create mode 100644 libs/web/state/ui/useRouterWarning.ts diff --git a/components/container/edit-container.tsx b/components/container/edit-container.tsx index 8e59a4af8..0dfe1259d 100644 --- a/components/container/edit-container.tsx +++ b/components/container/edit-container.tsx @@ -1,7 +1,7 @@ import NoteState from 'libs/web/state/note' import { has } from 'lodash' import router, { useRouter } from 'next/router' -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import NoteTreeState from 'libs/web/state/tree' import NoteNav from 'components/note-nav' import UIState from 'libs/web/state/ui' @@ -10,6 +10,7 @@ import useSettingsAPI from 'libs/web/api/settings' import dynamic from 'next/dynamic' import { useToast } from 'libs/web/hooks/use-toast' import DeleteAlert from 'components/editor/delete-alert' +import useRouterWarning from 'libs/web/state/ui/useRouterWarning' const MainEditor = dynamic(() => import('components/editor/main-editor')) @@ -27,6 +28,7 @@ export const EditContainer = () => { note, } = NoteState.useContainer() const { query } = useRouter() + const [isSaved, setSaved] = useState(true) const pid = query.pid as string const id = query.id as string const isNew = has(query, 'new') @@ -94,19 +96,25 @@ export const EditContainer = () => { useEffect(() => { abortFindNote() loadNoteById(id) + setSaved(true) }, [loadNoteById, abortFindNote, id]) useEffect(() => { - updateTitle(note?.title) - }, [note?.title, updateTitle]) - + updateTitle(`${(!isSaved && settings.explicitSave) ? "*" : ""}${note?.title}`) + }, [isSaved, note?.title, settings.explicitSave, updateTitle]) + + useRouterWarning(!isSaved && settings.explicitSave, () => { + return confirm("Warning! You have unsaved changes.") + }) + return ( <>
- +
+ ) } diff --git a/components/editor/editor.tsx b/components/editor/editor.tsx index 5fa5733ec..edf756e7f 100644 --- a/components/editor/editor.tsx +++ b/components/editor/editor.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useCallback, useEffect, useState } from 'react' import { use100vh } from 'react-div-100vh' import MarkdownEditor, { Props } from 'rich-markdown-editor' import { useEditorTheme } from './theme' @@ -11,10 +11,12 @@ import { useDictionary } from './dictionary' import { useEmbeds } from './embeds' export interface EditorProps extends Pick { - isPreview?: boolean + isPreview?: boolean, + explicitSave?: boolean, + saveState?: (state: boolean) => void, } -const Editor: FC = ({ readOnly, isPreview }) => { +const Editor: FC = ({ readOnly, isPreview, explicitSave, saveState }) => { const { onSearchLink, onCreateLink, @@ -39,6 +41,13 @@ const Editor: FC = ({ readOnly, isPreview }) => { setHasMinHeight((backlinks?.length ?? 0) <= 0) }, [backlinks, isPreview]) + const handleKeyDown = () => { + if (editorEl.current?.value) { + onEditorChange(editorEl.current?.value); + saveState && saveState(true) + } + } + return ( <> = ({ readOnly, isPreview }) => { id={note?.id} ref={editorEl} value={mounted ? note?.content : ''} - onChange={onEditorChange} + onChange={explicitSave ? (() => saveState && saveState(false)) : onEditorChange} + onSave={explicitSave ? handleKeyDown : undefined} placeholder={dictionary.editorPlaceholder} theme={editorTheme} uploadImage={(file) => onUploadImage(file, note?.id)} diff --git a/components/settings/explicit-save.tsx b/components/settings/explicit-save.tsx new file mode 100644 index 000000000..d5b803908 --- /dev/null +++ b/components/settings/explicit-save.tsx @@ -0,0 +1,36 @@ +import { FC, useCallback, ChangeEvent } from 'react' +import { MenuItem, TextField } from '@material-ui/core' +import router from 'next/router' +import { defaultFieldConfig } from './settings-container' +import useI18n from 'libs/web/hooks/use-i18n' +import UIState from 'libs/web/state/ui' + +export const ExplicitSave: FC = () => { + const { t } = useI18n() + const { + settings: { settings, updateSettings }, + } = UIState.useContainer() + + const handleChange = useCallback( + async (event: ChangeEvent) => { + await updateSettings({ explicitSave: Boolean(event.target.value) }) + console.log({ explicitSave: Boolean(event.target.value) }) + router.reload() + }, + [updateSettings] + ) + + console.log(settings) + return ( + + {t('Explicit save (ctrl + s)')} + {t('Auto save')} + + ) +} diff --git a/components/settings/settings-container.tsx b/components/settings/settings-container.tsx index bb5331e0e..a8ccf78b0 100644 --- a/components/settings/settings-container.tsx +++ b/components/settings/settings-container.tsx @@ -1,4 +1,4 @@ -import { TextFieldProps } from '@material-ui/core' +import { Checkbox, TextFieldProps } from '@material-ui/core' import { FC } from 'react' import { DailyNotes } from './daily-notes' import { Language } from './language' @@ -8,6 +8,7 @@ import { ImportOrExport } from './import-or-export' import { SnippetInjection } from './snippet-injection' import useI18n from 'libs/web/hooks/use-i18n' import { SettingsHeader } from './settings-header' +import { ExplicitSave } from './explicit-save' export const defaultFieldConfig: TextFieldProps = { fullWidth: true, @@ -36,6 +37,7 @@ export const SettingsContainer: FC = () => { +
= {}) { @@ -60,5 +62,10 @@ export function formatSettings(body: Record = {}) { settings.editorsize = body.editorsize } + console.log("settings.ts here>", body.explicitSave, isBoolean(body.explicitSave)) + if (isBoolean(body.explicitSave)) { + settings.explicitSave = body.explicitSave + } + return settings } diff --git a/libs/web/state/ui/useRouterWarning.ts b/libs/web/state/ui/useRouterWarning.ts new file mode 100644 index 000000000..90186171b --- /dev/null +++ b/libs/web/state/ui/useRouterWarning.ts @@ -0,0 +1,31 @@ +import Router from "next/router" +import { useEffect } from "react" + +export default function useRouterWarning(changes: boolean, callback: () => boolean) { + useEffect(() => { + if (!changes) { + return + } + const routeChangeStartCallback = () => { + const ok = callback() + if (!ok) { + Router.events.emit("routeChangeError") + throw "Abort route changes due to unsaved changes" + } + } + Router.events.on("routeChangeStart", routeChangeStartCallback) + return () => Router.events.off("routeChangeStart", routeChangeStartCallback) + }, [changes, callback]) + + useEffect(() => { + if (!changes) { + return + } + const callback = (e: BeforeUnloadEvent) => { + e.preventDefault() + return true; + } + window.onbeforeunload = callback; + return () => { window.onbeforeunload = null } + }, [changes, callback]) +} From 224d76bd1469d788ee6f1badb9222320980096ff Mon Sep 17 00:00:00 2001 From: K0IN <19688162+K0IN@users.noreply.github.com> Date: Sun, 14 Nov 2021 17:14:41 +0100 Subject: [PATCH 2/8] Removed debug line(s) * Removed debug statements --- components/settings/explicit-save.tsx | 2 -- libs/shared/settings.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/components/settings/explicit-save.tsx b/components/settings/explicit-save.tsx index d5b803908..e0c85a3ca 100644 --- a/components/settings/explicit-save.tsx +++ b/components/settings/explicit-save.tsx @@ -14,13 +14,11 @@ export const ExplicitSave: FC = () => { const handleChange = useCallback( async (event: ChangeEvent) => { await updateSettings({ explicitSave: Boolean(event.target.value) }) - console.log({ explicitSave: Boolean(event.target.value) }) router.reload() }, [updateSettings] ) - console.log(settings) return ( = {}) { settings.editorsize = body.editorsize } - console.log("settings.ts here>", body.explicitSave, isBoolean(body.explicitSave)) if (isBoolean(body.explicitSave)) { settings.explicitSave = body.explicitSave } From 100d3f33297c354def152526e5abe68815cefba3 Mon Sep 17 00:00:00 2001 From: K0IN <19688162+K0IN@users.noreply.github.com> Date: Mon, 15 Nov 2021 23:46:31 +0100 Subject: [PATCH 3/8] Fixed, Linting and formating --- components/container/edit-container.tsx | 18 +++-- components/editor/editor.tsx | 23 ++++-- components/icon-button.tsx | 2 +- components/note-nav.tsx | 2 +- .../portal/link-toolbar/link-toolbar.tsx | 4 +- .../portal/sidebar-menu/sidebar-menu-item.tsx | 6 +- .../portal/sidebar-menu/sidebar-menu.tsx | 2 +- components/settings/settings-container.tsx | 2 +- components/sidebar/sidebar-list.tsx | 2 +- libs/shared/meta.ts | 7 +- libs/shared/settings.ts | 6 +- libs/web/hooks/use-mounted.ts | 2 +- libs/web/state/ui/useRouterWarning.ts | 67 +++++++++-------- locales/ar.json | 2 +- locales/de-DE.json | 2 +- locales/fr-FR.json | 2 +- locales/it-IT.json | 2 +- locales/nl-NL.json | 2 +- locales/ru-RU.json | 2 +- locales/zh-CN.json | 2 +- next-env.d.ts | 12 +-- next.config.js | 2 +- scripts/cache.js | 74 +++++++++---------- 23 files changed, 136 insertions(+), 109 deletions(-) diff --git a/components/container/edit-container.tsx b/components/container/edit-container.tsx index 0dfe1259d..069aaf2b4 100644 --- a/components/container/edit-container.tsx +++ b/components/container/edit-container.tsx @@ -18,6 +18,7 @@ export const EditContainer = () => { const { title: { updateTitle }, settings: { settings }, + ua: { isMobile, isTablet, isWechat }, } = UIState.useContainer() const { genNewId } = NoteTreeState.useContainer() const { @@ -100,21 +101,26 @@ export const EditContainer = () => { }, [loadNoteById, abortFindNote, id]) useEffect(() => { - updateTitle(`${(!isSaved && settings.explicitSave) ? "*" : ""}${note?.title}`) + updateTitle(`${!isSaved && settings.explicitSave ? '*' : ''}${note?.title}`) }, [isSaved, note?.title, settings.explicitSave, updateTitle]) - + useRouterWarning(!isSaved && settings.explicitSave, () => { - return confirm("Warning! You have unsaved changes.") + return confirm('Warning! You have unsaved changes.') }) - + return ( <>
- +
- ) } diff --git a/components/editor/editor.tsx b/components/editor/editor.tsx index edf756e7f..fb53898aa 100644 --- a/components/editor/editor.tsx +++ b/components/editor/editor.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useState } from 'react' +import { FC, useEffect, useState } from 'react' import { use100vh } from 'react-div-100vh' import MarkdownEditor, { Props } from 'rich-markdown-editor' import { useEditorTheme } from './theme' @@ -11,12 +11,17 @@ import { useDictionary } from './dictionary' import { useEmbeds } from './embeds' export interface EditorProps extends Pick { - isPreview?: boolean, - explicitSave?: boolean, - saveState?: (state: boolean) => void, + isPreview?: boolean + explicitSave?: boolean + saveState?: (state: boolean) => void } -const Editor: FC = ({ readOnly, isPreview, explicitSave, saveState }) => { +const Editor: FC = ({ + readOnly, + isPreview, + explicitSave, + saveState, +}) => { const { onSearchLink, onCreateLink, @@ -43,8 +48,8 @@ const Editor: FC = ({ readOnly, isPreview, explicitSave, saveState const handleKeyDown = () => { if (editorEl.current?.value) { - onEditorChange(editorEl.current?.value); - saveState && saveState(true) + onEditorChange(editorEl.current?.value) + saveState && saveState(true) } } @@ -55,7 +60,9 @@ const Editor: FC = ({ readOnly, isPreview, explicitSave, saveState id={note?.id} ref={editorEl} value={mounted ? note?.content : ''} - onChange={explicitSave ? (() => saveState && saveState(false)) : onEditorChange} + onChange={ + explicitSave ? () => saveState && saveState(false) : onEditorChange + } onSave={explicitSave ? handleKeyDown : undefined} placeholder={dictionary.editorPlaceholder} theme={editorTheme} diff --git a/components/icon-button.tsx b/components/icon-button.tsx index 48ebfc48a..30d25370a 100644 --- a/components/icon-button.tsx +++ b/components/icon-button.tsx @@ -18,7 +18,7 @@ import { ExternalLinkIcon, BookmarkAltIcon, PuzzleIcon, - ChevronDoubleUpIcon + ChevronDoubleUpIcon, } from '@heroicons/react/outline' export const ICONS = { diff --git a/components/note-nav.tsx b/components/note-nav.tsx index bfcd884ef..2bcb848db 100644 --- a/components/note-nav.tsx +++ b/components/note-nav.tsx @@ -61,7 +61,7 @@ const NoteNav = () => { const handleClickOpenInTree = useCallback(() => { if (!note) return - showItem(note); + showItem(note) }, [note, showItem]) return ( diff --git a/components/portal/link-toolbar/link-toolbar.tsx b/components/portal/link-toolbar/link-toolbar.tsx index 2acd84c8c..272cf8bce 100644 --- a/components/portal/link-toolbar/link-toolbar.tsx +++ b/components/portal/link-toolbar/link-toolbar.tsx @@ -31,7 +31,9 @@ const LinkToolbar = () => { if (!result) { return } - const bookmarkUrl = `/api/extract?type=${type}&url=${encodeURIComponent(href)}` + const bookmarkUrl = `/api/extract?type=${type}&url=${encodeURIComponent( + href + )}` const transaction = state.tr.replaceWith( result.pos, result.pos + result.node.nodeSize, diff --git a/components/portal/sidebar-menu/sidebar-menu-item.tsx b/components/portal/sidebar-menu/sidebar-menu-item.tsx index c3873189d..e07f50576 100644 --- a/components/portal/sidebar-menu/sidebar-menu-item.tsx +++ b/components/portal/sidebar-menu/sidebar-menu-item.tsx @@ -27,7 +27,9 @@ interface ItemProps { export const SidebarMenuItem = forwardRef( ({ item }, ref) => { - const { settings: { settings } } = UIState.useContainer() + const { + settings: { settings }, + } = UIState.useContainer() const { removeNote, mutateNote } = NoteState.useContainer() const { menu: { close, data }, @@ -66,7 +68,7 @@ export const SidebarMenuItem = forwardRef( }) } }, [close, data, mutateNote]) - + const toggleWidth = useCallback(() => { close() if (data?.id) { diff --git a/components/portal/sidebar-menu/sidebar-menu.tsx b/components/portal/sidebar-menu/sidebar-menu.tsx index f7ce404f9..8640ca43e 100644 --- a/components/portal/sidebar-menu/sidebar-menu.tsx +++ b/components/portal/sidebar-menu/sidebar-menu.tsx @@ -49,7 +49,7 @@ const SidebarMenu: FC = () => { { text: t('Toggle width'), // TODO: or SwitchHorizontal? - icon: , + icon: , handler: MENU_HANDLER_NAME.TOGGLE_WIDTH, }, ], diff --git a/components/settings/settings-container.tsx b/components/settings/settings-container.tsx index a8ccf78b0..5032b2087 100644 --- a/components/settings/settings-container.tsx +++ b/components/settings/settings-container.tsx @@ -1,4 +1,4 @@ -import { Checkbox, TextFieldProps } from '@material-ui/core' +import { TextFieldProps } from '@material-ui/core' import { FC } from 'react' import { DailyNotes } from './daily-notes' import { Language } from './language' diff --git a/components/sidebar/sidebar-list.tsx b/components/sidebar/sidebar-list.tsx index 996874c96..ace79015f 100644 --- a/components/sidebar/sidebar-list.tsx +++ b/components/sidebar/sidebar-list.tsx @@ -16,7 +16,7 @@ const SideBarList = () => { moveItem, mutateItem, initLoaded, - collapseAllItems + collapseAllItems, } = NoteTreeState.useContainer() const onExpand = useCallback( diff --git a/libs/shared/meta.ts b/libs/shared/meta.ts index 35a6a62e0..5fc939988 100644 --- a/libs/shared/meta.ts +++ b/libs/shared/meta.ts @@ -32,4 +32,9 @@ export const PAGE_META_KEY = [ export type metaKey = typeof PAGE_META_KEY[number] -export const NUMBER_KEYS: metaKey[] = ['deleted', 'shared', 'pinned', 'editorsize'] +export const NUMBER_KEYS: metaKey[] = [ + 'deleted', + 'shared', + 'pinned', + 'editorsize', +] diff --git a/libs/shared/settings.ts b/libs/shared/settings.ts index 577262c0e..be32e66f5 100644 --- a/libs/shared/settings.ts +++ b/libs/shared/settings.ts @@ -10,8 +10,8 @@ export interface Settings { last_visit?: string locale: Locale injection?: string - editorsize: EDITOR_SIZE; - explicitSave: boolean; + editorsize: EDITOR_SIZE + explicitSave: boolean } export const DEFAULT_SETTINGS: Settings = Object.freeze({ @@ -20,7 +20,7 @@ export const DEFAULT_SETTINGS: Settings = Object.freeze({ sidebar_is_fold: false, locale: Locale.EN, editorsize: EDITOR_SIZE.SMALL, - explicitSave: false + explicitSave: false, }) export function formatSettings(body: Record = {}) { diff --git a/libs/web/hooks/use-mounted.ts b/libs/web/hooks/use-mounted.ts index 8c8cda891..1b85833da 100644 --- a/libs/web/hooks/use-mounted.ts +++ b/libs/web/hooks/use-mounted.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' - const useMounted = () => { +const useMounted = () => { const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) diff --git a/libs/web/state/ui/useRouterWarning.ts b/libs/web/state/ui/useRouterWarning.ts index 90186171b..fe2f9e2ae 100644 --- a/libs/web/state/ui/useRouterWarning.ts +++ b/libs/web/state/ui/useRouterWarning.ts @@ -1,31 +1,36 @@ -import Router from "next/router" -import { useEffect } from "react" - -export default function useRouterWarning(changes: boolean, callback: () => boolean) { - useEffect(() => { - if (!changes) { - return - } - const routeChangeStartCallback = () => { - const ok = callback() - if (!ok) { - Router.events.emit("routeChangeError") - throw "Abort route changes due to unsaved changes" - } - } - Router.events.on("routeChangeStart", routeChangeStartCallback) - return () => Router.events.off("routeChangeStart", routeChangeStartCallback) - }, [changes, callback]) - - useEffect(() => { - if (!changes) { - return - } - const callback = (e: BeforeUnloadEvent) => { - e.preventDefault() - return true; - } - window.onbeforeunload = callback; - return () => { window.onbeforeunload = null } - }, [changes, callback]) -} +import Router from 'next/router' +import { useEffect } from 'react' + +export default function useRouterWarning( + changes: boolean, + callback: () => boolean +) { + useEffect(() => { + if (!changes) { + return + } + const routeChangeStartCallback = () => { + const ok = callback() + if (!ok) { + Router.events.emit('routeChangeError') + throw 'Abort route changes due to unsaved changes' + } + } + Router.events.on('routeChangeStart', routeChangeStartCallback) + return () => Router.events.off('routeChangeStart', routeChangeStartCallback) + }, [changes, callback]) + + useEffect(() => { + if (!changes) { + return + } + const callback = (e: BeforeUnloadEvent) => { + e.preventDefault() + return true + } + window.onbeforeunload = callback + return () => { + window.onbeforeunload = null + } + }, [changes, callback]) +} diff --git a/locales/ar.json b/locales/ar.json index f87684a1e..794ed8eb0 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -116,4 +116,4 @@ "Warning": "تحذير", "Warning notice": "إشعار التحذير", "Write something nice…": "اكتب شيئًا لطيفًا …" -} \ No newline at end of file +} diff --git a/locales/de-DE.json b/locales/de-DE.json index 5f5b0e6d9..f10a3b7e7 100644 --- a/locales/de-DE.json +++ b/locales/de-DE.json @@ -116,4 +116,4 @@ "Warning": "Warning", "Warning notice": "Warning notice", "Write something nice…": "Write something nice…" -} \ No newline at end of file +} diff --git a/locales/fr-FR.json b/locales/fr-FR.json index 7e69a37f5..28a97114a 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -116,4 +116,4 @@ "Warning": "Attention", "Warning notice": "Attention", "Write something nice…": "Ecrivez quelquechose de bien..." -} \ No newline at end of file +} diff --git a/locales/it-IT.json b/locales/it-IT.json index c0fe8a495..eeec3b8cb 100644 --- a/locales/it-IT.json +++ b/locales/it-IT.json @@ -116,4 +116,4 @@ "Warning": "Attenzione", "Warning notice": "Nota d'attenzione", "Write something nice…": "Scrivi qualcosa di carino…" -} \ No newline at end of file +} diff --git a/locales/nl-NL.json b/locales/nl-NL.json index 02c4c4564..0ed8ddcfa 100644 --- a/locales/nl-NL.json +++ b/locales/nl-NL.json @@ -116,4 +116,4 @@ "Warning": "Waarschuwing", "Warning notice": "Waarschuwing blok", "Write something nice…": "Schrijf iets leuks…" -} \ No newline at end of file +} diff --git a/locales/ru-RU.json b/locales/ru-RU.json index 414bd7ba6..4e0a2fee4 100644 --- a/locales/ru-RU.json +++ b/locales/ru-RU.json @@ -116,4 +116,4 @@ "Warning": "Warning", "Warning notice": "Warning notice", "Write something nice…": "Write something nice…" -} \ No newline at end of file +} diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 8c205bcc5..bb13e77ef 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -116,4 +116,4 @@ "Warning": "警告", "Warning notice": "警告信息", "Write something nice…": "写点什么..." -} \ No newline at end of file +} diff --git a/next-env.d.ts b/next-env.d.ts index 9bc3dd46b..534a39ea7 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ -/// -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index 469806e42..52f0bcb03 100644 --- a/next.config.js +++ b/next.config.js @@ -7,6 +7,6 @@ module.exports = withPWA({ pwa: { disable: process.env.NODE_ENV === 'development', dest: 'public', - runtimeCaching: cache + runtimeCaching: cache, }, }) diff --git a/scripts/cache.js b/scripts/cache.js index 912650bab..0cc2b839d 100644 --- a/scripts/cache.js +++ b/scripts/cache.js @@ -31,19 +31,19 @@ module.exports = [ cacheName: 'static-font-assets', expiration: { maxEntries: 4, - maxAgeSeconds: 7 * 24 * 60 * 60 // 7 days - } - } + maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days + }, + }, }, { urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i, - handler: 'NetworkOnly' + handler: 'NetworkOnly', }, { urlPattern: /\/_next\/image\?url=.+$/i, - handler: "StaleWhileRevalidate", + handler: 'StaleWhileRevalidate', options: { - cacheName: "next-image", + cacheName: 'next-image', expiration: { maxEntries: 64, maxAgeSeconds: 24 * 60 * 60, // 24 hours @@ -58,9 +58,9 @@ module.exports = [ cacheName: 'static-audio-assets', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours - } - } + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, }, { urlPattern: /\.(?:mp4)$/i, @@ -70,9 +70,9 @@ module.exports = [ cacheName: 'static-video-assets', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours - } - } + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, }, { urlPattern: /\.(?:js)$/i, @@ -81,9 +81,9 @@ module.exports = [ cacheName: 'static-js-assets', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours - } - } + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, }, { urlPattern: /\.(?:css|less)$/i, @@ -92,19 +92,19 @@ module.exports = [ cacheName: 'static-style-assets', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours - } - } + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, }, { urlPattern: /\/_next\/data\/.+\/.+\.json$/i, - handler: "StaleWhileRevalidate", + handler: 'StaleWhileRevalidate', options: { - cacheName: "next-data", + cacheName: 'next-data', expiration: { maxEntries: 32, maxAgeSeconds: 24 * 60 * 60, // 24 hours - } + }, }, }, { @@ -114,12 +114,12 @@ module.exports = [ cacheName: 'static-data-assets', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours - } - } + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, }, { - urlPattern: ({url}) => { + urlPattern: ({ url }) => { const isSameOrigin = self.origin === url.origin if (!isSameOrigin) return false const pathname = url.pathname @@ -136,13 +136,13 @@ module.exports = [ cacheName: 'apis', expiration: { maxEntries: 16, - maxAgeSeconds: 24 * 60 * 60 // 24 hours + maxAgeSeconds: 24 * 60 * 60, // 24 hours }, - networkTimeoutSeconds: 10 // fall back to cache if api does not response within 10 seconds - } + networkTimeoutSeconds: 10, // fall back to cache if api does not response within 10 seconds + }, }, { - urlPattern: ({url}) => { + urlPattern: ({ url }) => { const isSameOrigin = self.origin === url.origin if (!isSameOrigin) return false const pathname = url.pathname @@ -154,13 +154,13 @@ module.exports = [ cacheName: 'others', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 24 hours + maxAgeSeconds: 24 * 60 * 60, // 24 hours }, - networkTimeoutSeconds: 10 - } + networkTimeoutSeconds: 10, + }, }, { - urlPattern: ({url}) => { + urlPattern: ({ url }) => { const isSameOrigin = self.origin === url.origin return !isSameOrigin }, @@ -169,9 +169,9 @@ module.exports = [ cacheName: 'cross-origin', expiration: { maxEntries: 32, - maxAgeSeconds: 60 * 60 // 1 hour + maxAgeSeconds: 60 * 60, // 1 hour }, - networkTimeoutSeconds: 10 - } - } + networkTimeoutSeconds: 10, + }, + }, ] From e597189e00c0b2816b0c26cf79d0376dc37a49fa Mon Sep 17 00:00:00 2001 From: K0IN <19688162+K0IN@users.noreply.github.com> Date: Wed, 24 Nov 2021 18:01:29 +0100 Subject: [PATCH 4/8] Added Save Button * Added save icon * Added button for mobil devices * Refactored saving --- components/container/edit-container.tsx | 12 +++++++++--- components/editor/editor.tsx | 23 ++++++++++++++--------- components/icon-button.tsx | 2 ++ components/note-nav.tsx | 19 +++++++++++++++++-- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/components/container/edit-container.tsx b/components/container/edit-container.tsx index 069aaf2b4..1c5af23a0 100644 --- a/components/container/edit-container.tsx +++ b/components/container/edit-container.tsx @@ -1,7 +1,7 @@ import NoteState from 'libs/web/state/note' import { has } from 'lodash' import router, { useRouter } from 'next/router' -import { useCallback, useEffect, useState } from 'react' +import { RefObject, useCallback, useEffect, useRef, useState } from 'react' import NoteTreeState from 'libs/web/state/tree' import NoteNav from 'components/note-nav' import UIState from 'libs/web/state/ui' @@ -35,6 +35,7 @@ export const EditContainer = () => { const isNew = has(query, 'new') const { mutate: mutateSettings } = useSettingsAPI() const toast = useToast() + const saveRef = useRef<() => void>() as RefObject const loadNoteById = useCallback( async (id: string) => { @@ -110,15 +111,20 @@ export const EditContainer = () => { return ( <> - +
diff --git a/components/editor/editor.tsx b/components/editor/editor.tsx index fb53898aa..31a916450 100644 --- a/components/editor/editor.tsx +++ b/components/editor/editor.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, Ref, useEffect, useState } from 'react' import { use100vh } from 'react-div-100vh' import MarkdownEditor, { Props } from 'rich-markdown-editor' import { useEditorTheme } from './theme' @@ -13,7 +13,8 @@ import { useEmbeds } from './embeds' export interface EditorProps extends Pick { isPreview?: boolean explicitSave?: boolean - saveState?: (state: boolean) => void + saveState?: (state: boolean) => void, + saveRef?: Ref<() => void> } const Editor: FC = ({ @@ -21,6 +22,7 @@ const Editor: FC = ({ isPreview, explicitSave, saveState, + saveRef }) => { const { onSearchLink, @@ -46,13 +48,16 @@ const Editor: FC = ({ setHasMinHeight((backlinks?.length ?? 0) <= 0) }, [backlinks, isPreview]) - const handleKeyDown = () => { - if (editorEl.current?.value) { - onEditorChange(editorEl.current?.value) - saveState && saveState(true) + useEffect(() => { + const handleKeyDown = () => { + if (editorEl.current?.value) { + onEditorChange(editorEl.current?.value) + saveState && saveState(true) + } } - } - + if (saveRef) (saveRef as any).current = handleKeyDown; + }, [saveState, saveRef, editorEl, onEditorChange] ) + return ( <> = ({ onChange={ explicitSave ? () => saveState && saveState(false) : onEditorChange } - onSave={explicitSave ? handleKeyDown : undefined} + onSave={explicitSave ? (() => (saveRef as any)?.current?.()) : undefined} placeholder={dictionary.editorPlaceholder} theme={editorTheme} uploadImage={(file) => onUploadImage(file, note?.id)} diff --git a/components/icon-button.tsx b/components/icon-button.tsx index 30d25370a..090c11f43 100644 --- a/components/icon-button.tsx +++ b/components/icon-button.tsx @@ -19,6 +19,7 @@ import { BookmarkAltIcon, PuzzleIcon, ChevronDoubleUpIcon, + SaveIcon } from '@heroicons/react/outline' export const ICONS = { @@ -40,6 +41,7 @@ export const ICONS = { BookmarkAlt: BookmarkAltIcon, Puzzle: PuzzleIcon, ChevronDoubleUp: ChevronDoubleUpIcon, + Save: SaveIcon } const IconButton = forwardRef< diff --git a/components/note-nav.tsx b/components/note-nav.tsx index 2bcb848db..d19e869e4 100644 --- a/components/note-nav.tsx +++ b/components/note-nav.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import NoteState from 'libs/web/state/note' import UIState from 'libs/web/state/ui' -import { useCallback, MouseEvent } from 'react' +import { useCallback, MouseEvent, RefObject } from 'react' import { CircularProgress, Tooltip } from '@material-ui/core' import NoteTreeState from 'libs/web/state/tree' import { Breadcrumbs } from '@material-ui/core' @@ -33,8 +33,13 @@ const MenuButton = () => { > ) } +type Props = { + explicitSave?: boolean, + isSaved?: boolean, + saveRef: RefObject<() => void> +} -const NoteNav = () => { +const NoteNav = ({explicitSave, isSaved, saveRef}: Props) => { const { t } = useI18n() const { note, loading } = NoteState.useContainer() const { ua } = UIState.useContainer() @@ -64,6 +69,8 @@ const NoteNav = () => { showItem(note) }, [note, showItem]) + const saveNote = useCallback(() => saveRef?.current?.(), [saveRef]) + return (