mirror of
https://github.com/muerwre/markdown-home-tab.git
synced 2025-04-24 16:36:41 +07:00
add sync storage
This commit is contained in:
parent
b0665bfb2a
commit
6d00bffbec
14 changed files with 164 additions and 82 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,3 +22,4 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
web-ext-artifacts
|
|
@ -1 +0,0 @@
|
||||||
nodejs 18.0.0
|
|
|
@ -6,10 +6,9 @@ Markdown Editor for new tab made with [Milkdown](https://milkdown.dev/) and
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```
|
```
|
||||||
|
yarn global add web-ext
|
||||||
yarn
|
yarn
|
||||||
yarn build
|
yarn package
|
||||||
cd ./dist
|
|
||||||
web-ext build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "markdown-home-tab",
|
"name": "markdown-home-tab",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"package": "yarn build && cd dist && web-ext build",
|
"package": "yarn build && web-ext build -s ./dist",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
@ -34,8 +34,10 @@
|
||||||
"web-ext": "^7.6.1"
|
"web-ext": "^7.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chrome": "^0.0.273",
|
||||||
"@types/classnames": "^2.3.1",
|
"@types/classnames": "^2.3.1",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
|
"@types/firefox-webext-browser": "^120.0.4",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Markdown Home Tab",
|
"name": "Markdown Home Tab",
|
||||||
"short_name": "Markdown New Tab",
|
"short_name": "Markdown New Tab",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"description": "Markdown right in your home tab! Paste links, pictures, lists and more. You can also customize colors to match your needs.",
|
"description": "Markdown right in your home tab! Paste links, pictures, lists and more. You can also customize colors to match your needs.",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
|
|
|
@ -1,35 +1,26 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { BrowserSyncStorage } from '~/utils/index';
|
||||||
const safelyGetStringValue = (key: string) => {
|
|
||||||
try {
|
|
||||||
return localStorage.getItem(key) ?? "";
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const safelySetStringValue = (key: string, value: string) => {
|
|
||||||
try {
|
|
||||||
return localStorage.setItem(key, value);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePersistedValue = (
|
export const usePersistedValue = (
|
||||||
id: string,
|
id: string,
|
||||||
prefix: string
|
prefix: string
|
||||||
): [string, (val: string) => void] => {
|
) => {
|
||||||
|
const [hydrated, setHydrated] = useState(false);
|
||||||
|
const storage = useMemo(() => new BrowserSyncStorage(prefix), [prefix]);
|
||||||
const key = `${prefix}${id}`;
|
const key = `${prefix}${id}`;
|
||||||
const [value, setValue] = useState(safelyGetStringValue(key));
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(safelyGetStringValue(key));
|
storage.get<string>(key).then(val => setValue(val ?? '')).finally(() => setHydrated(true));
|
||||||
}, [id, key]);
|
}, [key, storage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
safelySetStringValue(key, value);
|
if (!hydrated) {
|
||||||
}, [key, value]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return [value, setValue];
|
storage.set(key, value);
|
||||||
|
}, [key, value, storage, hydrated]);
|
||||||
|
|
||||||
|
return { value, setValue, hydrated };
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,10 @@ export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
||||||
settings: { richEditorEnabled },
|
settings: { richEditorEnabled },
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
|
|
||||||
const [value, setValue] = usePersistedValue(id, "MarkdownEditorContainer");
|
const { value, setValue, hydrated } = usePersistedValue(
|
||||||
|
id,
|
||||||
|
"MarkdownEditorContainer"
|
||||||
|
);
|
||||||
|
|
||||||
const empty = !value.trim();
|
const empty = !value.trim();
|
||||||
|
|
||||||
|
@ -52,7 +55,7 @@ export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.editor}>
|
<div className={styles.editor}>
|
||||||
<Suspense>{locked ? viewer : editor}</Suspense>
|
{hydrated && <Suspense>{locked ? viewer : editor}</Suspense>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
import { DockviewApi, DockviewReadyEvent } from "dockview";
|
import { DockviewApi, DockviewReadyEvent, SerializedDockview } from "dockview";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { createDefaultLayout } from "../utils/createDefaultLayout";
|
import { createDefaultLayout } from "../utils/createDefaultLayout";
|
||||||
|
import { BrowserSyncStorage } from "~/utils";
|
||||||
|
|
||||||
|
const storage = new BrowserSyncStorage();
|
||||||
|
const key = 'dockview_persistance_layout';
|
||||||
|
|
||||||
export const useGridLayoutPersistance = () => {
|
export const useGridLayoutPersistance = () => {
|
||||||
const api = useRef<DockviewApi>();
|
const api = useRef<DockviewApi>();
|
||||||
|
const [hydrated, setHydrated] = useState(false);
|
||||||
|
|
||||||
const onReady = (event: DockviewReadyEvent) => {
|
const onReady = (event: DockviewReadyEvent) => {
|
||||||
api.current = event.api;
|
api.current = event.api;
|
||||||
|
|
||||||
const layoutString = localStorage.getItem("dockview_persistance_layout");
|
storage.get<SerializedDockview>(key).then(layout => {
|
||||||
|
if (!layout) {
|
||||||
|
throw new Error("No layout saved, its okay");
|
||||||
|
}
|
||||||
|
|
||||||
if (!layoutString) {
|
|
||||||
createDefaultLayout(event.api);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const layout = JSON.parse(layoutString);
|
|
||||||
event.api.fromJSON(layout);
|
event.api.fromJSON(layout);
|
||||||
} catch (err) {
|
}).catch(() => {
|
||||||
console.log(err);
|
|
||||||
createDefaultLayout(event.api);
|
createDefaultLayout(event.api);
|
||||||
}
|
|
||||||
|
}).finally(() => {
|
||||||
|
setHydrated(true);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistLayout = useCallback(() => {
|
const persistLayout = useCallback(() => {
|
||||||
|
@ -29,18 +32,12 @@ export const useGridLayoutPersistance = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout = api.current.toJSON();
|
storage.set(key, api.current.toJSON());
|
||||||
|
|
||||||
localStorage.setItem("dockview_persistance_layout", JSON.stringify(layout));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!api.current) {
|
const onLayoutChange = api.current?.onDidLayoutChange(() => {
|
||||||
return;
|
if (!api.current || !hydrated) {
|
||||||
}
|
|
||||||
|
|
||||||
const onLayoutChange = api.current.onDidLayoutChange(() => {
|
|
||||||
if (!api.current) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +48,8 @@ export const useGridLayoutPersistance = () => {
|
||||||
persistLayout();
|
persistLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return onLayoutChange?.dispose;
|
||||||
onLayoutChange.dispose();
|
}, [persistLayout, hydrated]);
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [persistLayout, api.current]);
|
|
||||||
|
|
||||||
return { api, onReady, persistLayout };
|
return { api, onReady, persistLayout, hydrated };
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,33 +1,37 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
||||||
import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme";
|
import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme";
|
||||||
|
import { BrowserSyncStorage } from "~/utils";
|
||||||
import { defaultSettings } from "../context/SettingsContext";
|
import { defaultSettings } from "../context/SettingsContext";
|
||||||
|
|
||||||
const getLocalStorageSettings = (defaultValue: SettingsValue) => {
|
const storage = new BrowserSyncStorage('settings');
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem("settings");
|
|
||||||
const parsed = JSON.parse(raw ?? "");
|
|
||||||
|
|
||||||
return parsed ? { ...defaultValue, ...parsed } : defaultValue;
|
|
||||||
} catch (error) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePersistSettings = () => {
|
export const usePersistSettings = () => {
|
||||||
|
const [hydrated, setHydrated] = useState(false);
|
||||||
|
|
||||||
const defaultColors = useDefaultTheme();
|
const defaultColors = useDefaultTheme();
|
||||||
|
|
||||||
const [settings, setSettings] = useState<SettingsValue>(
|
const [settings, setSettings] = useState({
|
||||||
getLocalStorageSettings({ ...defaultSettings, ...defaultColors })
|
...defaultSettings, ...defaultColors
|
||||||
);
|
});
|
||||||
|
|
||||||
const update = useCallback((val: Partial<SettingsValue>) => {
|
const update = useCallback((val: Partial<SettingsValue>) => {
|
||||||
|
if (!hydrated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSettings((v) => {
|
setSettings((v) => {
|
||||||
const updatedValue = { ...v, ...val };
|
const updatedValue = { ...v, ...val };
|
||||||
localStorage.setItem("settings", JSON.stringify(updatedValue));
|
storage.set("", updatedValue);
|
||||||
return updatedValue;
|
return updatedValue;
|
||||||
});
|
});
|
||||||
}, []);
|
}, [hydrated]);
|
||||||
|
|
||||||
return { settings, update };
|
useEffect(() => {
|
||||||
|
storage.get<SettingsValue>("").then(next => {
|
||||||
|
setSettings(prev => ({ ...prev, ...next }));
|
||||||
|
}).finally(() => setHydrated(true));
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { settings, update, hydrated };
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,13 +11,17 @@ import styles from "./styles.module.scss";
|
||||||
const SettingsProvider: FC<PropsWithChildren> = ({ children }) => {
|
const SettingsProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { settings, update } = usePersistSettings();
|
const { settings, update, hydrated } = usePersistSettings();
|
||||||
|
|
||||||
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
||||||
|
|
||||||
const show = useCallback(() => setSettingsModalVisible(true), []);
|
const show = useCallback(() => setSettingsModalVisible(true), []);
|
||||||
const hide = useCallback(() => setSettingsModalVisible(false), []);
|
const hide = useCallback(() => setSettingsModalVisible(false), []);
|
||||||
|
|
||||||
|
if (!hydrated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider value={{ settings, update, show, hide }}>
|
<SettingsContext.Provider value={{ settings, update, show, hide }}>
|
||||||
{settingsModalVisible && (
|
{settingsModalVisible && (
|
||||||
|
|
|
@ -13,8 +13,6 @@ export const useThemeColors = (settings: ColorSettings) => {
|
||||||
|
|
||||||
const code = new Color(settings.codeColor);
|
const code = new Color(settings.codeColor);
|
||||||
|
|
||||||
console.log(code.hex());
|
|
||||||
|
|
||||||
// Backgrounds and text
|
// Backgrounds and text
|
||||||
document.body.style.setProperty("--color-background", background.hex());
|
document.body.style.setProperty("--color-background", background.hex());
|
||||||
document.body.style.setProperty("--color-code-text", code.hex());
|
document.body.style.setProperty("--color-code-text", code.hex());
|
||||||
|
|
57
src/utils/index.ts
Normal file
57
src/utils/index.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
export class BrowserSyncStorage {
|
||||||
|
constructor(private globalPrefix = "") {}
|
||||||
|
|
||||||
|
get engine() {
|
||||||
|
if (typeof browser !== 'undefined' && browser?.storage) {
|
||||||
|
return "browser"
|
||||||
|
} else if (typeof chrome !== 'undefined' && chrome?.storage) {
|
||||||
|
return "chrome"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "local";
|
||||||
|
}
|
||||||
|
|
||||||
|
makeKey = (key: string) => `${this.globalPrefix}${key}`;
|
||||||
|
|
||||||
|
set = async <T>(key: string, value: T) => {
|
||||||
|
switch (this.engine) {
|
||||||
|
case 'browser':
|
||||||
|
await browser.storage.sync.set({ [this.makeKey(key)]: value });
|
||||||
|
return;
|
||||||
|
case 'chrome':
|
||||||
|
await chrome.storage.sync.set({ [this.makeKey(key)]: value });
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
localStorage.setItem(this.makeKey(key), JSON.stringify(value));
|
||||||
|
return
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get = async <T>(key: string): Promise<T | undefined> => {
|
||||||
|
if (this.engine === 'browser') {
|
||||||
|
const value = await browser.storage.sync
|
||||||
|
.get([this.makeKey(key)])
|
||||||
|
.then((result) => result[this.makeKey(key)] as T | undefined);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} else if (this.engine === 'chrome') {
|
||||||
|
const value = await chrome.storage.sync.get(this.makeKey(key)).then(
|
||||||
|
(result) => result[this.makeKey(key)] as T | undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(this.makeKey(key));
|
||||||
|
return value ? (JSON.parse(value) as T) : undefined;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -27,6 +27,6 @@
|
||||||
},
|
},
|
||||||
"types": ["./src/vite-env.d.ts", "./src/i18next.d.ts"]
|
"types": ["./src/vite-env.d.ts", "./src/i18next.d.ts"]
|
||||||
},
|
},
|
||||||
"include": ["src", "public/locales"],
|
"include": ["src", "public/locales", "./node_modules/@types/**/*"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
30
yarn.lock
30
yarn.lock
|
@ -1859,6 +1859,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect "^2.0.1"
|
defer-to-connect "^2.0.1"
|
||||||
|
|
||||||
|
"@types/chrome@^0.0.273":
|
||||||
|
version "0.0.273"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.273.tgz#986f5090de34cdd8ca467431e9f9c3c12e951407"
|
||||||
|
integrity sha512-6Wp4GO07GLvti13Rf/RpYG+0COSJDOLE4iq3g1+whn1SNGUVnv6vbXqSa/WFbuVpvN1lcBLiZ40+gSeWmKb+eA==
|
||||||
|
dependencies:
|
||||||
|
"@types/filesystem" "*"
|
||||||
|
"@types/har-format" "*"
|
||||||
|
|
||||||
"@types/classnames@^2.3.1":
|
"@types/classnames@^2.3.1":
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd"
|
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd"
|
||||||
|
@ -1909,6 +1917,28 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
||||||
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
||||||
|
|
||||||
|
"@types/filesystem@*":
|
||||||
|
version "0.0.36"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857"
|
||||||
|
integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==
|
||||||
|
dependencies:
|
||||||
|
"@types/filewriter" "*"
|
||||||
|
|
||||||
|
"@types/filewriter@*":
|
||||||
|
version "0.0.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8"
|
||||||
|
integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==
|
||||||
|
|
||||||
|
"@types/firefox-webext-browser@^120.0.4":
|
||||||
|
version "120.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/firefox-webext-browser/-/firefox-webext-browser-120.0.4.tgz#27eead781051b2e681a344dd2983735faadd4343"
|
||||||
|
integrity sha512-lBrpf08xhiZBigrtdQfUaqX1UauwZ+skbFiL8u2Tdra/rklkKadYmIzTwkNZSWtuZ7OKpFqbE2HHfDoFqvZf6w==
|
||||||
|
|
||||||
|
"@types/har-format@*":
|
||||||
|
version "1.2.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.16.tgz#b71ede8681400cc08b3685f061c31e416cf94944"
|
||||||
|
integrity sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==
|
||||||
|
|
||||||
"@types/hast@^2.0.0":
|
"@types/hast@^2.0.0":
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
|
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue