Compare commits
No commits in common. "46e7f1aaea91f742dd816369fbed0e8dc85944cb" and "21afb718f7aa429e16cb61aa2107c1035143aa02" have entirely different histories.
46e7f1aaea
...
21afb718f7
14 changed files with 25 additions and 360 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
||||||
*.lockb binary diff=lockb
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -24,7 +24,6 @@
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
"vite-plugin-solid": "^2.10.2",
|
"vite-plugin-solid": "^2.10.2",
|
||||||
"vite-plugin-solid-styled": "^0.11.1",
|
"vite-plugin-solid-styled": "^0.11.1",
|
||||||
"workbox-build": "^7.1.1",
|
|
||||||
"wrangler": "^3.78.2"
|
"wrangler": "^3.78.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -50,9 +49,7 @@
|
||||||
"solid-js": "^1.8.22",
|
"solid-js": "^1.8.22",
|
||||||
"solid-styled": "^0.11.1",
|
"solid-styled": "^0.11.1",
|
||||||
"stacktrace-js": "^2.0.2",
|
"stacktrace-js": "^2.0.2",
|
||||||
"web-animations-js": "^2.3.2",
|
"web-animations-js": "^2.3.2"
|
||||||
"workbox-core": "^7.1.0",
|
|
||||||
"workbox-precaching": "^7.1.0"
|
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.1.21"
|
"packageManager": "bun@1.1.21"
|
||||||
}
|
}
|
||||||
|
|
56
src/App.tsx
56
src/App.tsx
|
@ -5,7 +5,6 @@ import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
createRenderEffect,
|
createRenderEffect,
|
||||||
createSignal,
|
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
lazy,
|
lazy,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
|
@ -18,14 +17,6 @@ import {
|
||||||
import { $accounts, updateAcctInf } from "./accounts/stores.js";
|
import { $accounts, updateAcctInf } from "./accounts/stores.js";
|
||||||
import { useStore } from "@nanostores/solid";
|
import { useStore } from "@nanostores/solid";
|
||||||
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
|
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/solid";
|
|
||||||
import {
|
|
||||||
isJSONRPCResult,
|
|
||||||
ResultDispatcher,
|
|
||||||
type JSONRPC,
|
|
||||||
} from "./serviceworker/services.js";
|
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener";
|
|
||||||
import { ServiceWorkerProvider } from "./platform/host.js";
|
|
||||||
|
|
||||||
const AccountSignIn = lazy(() => import("./accounts/SignIn.js"));
|
const AccountSignIn = lazy(() => import("./accounts/SignIn.js"));
|
||||||
const AccountMastodonOAuth2Callback = lazy(
|
const AccountMastodonOAuth2Callback = lazy(
|
||||||
|
@ -67,43 +58,6 @@ const App: Component = () => {
|
||||||
const theme = useRootTheme();
|
const theme = useRootTheme();
|
||||||
const accts = useStore($accounts);
|
const accts = useStore($accounts);
|
||||||
const lang = useLanguage();
|
const lang = useLanguage();
|
||||||
const [serviceWorker, setServiceWorker] = createSignal<ServiceWorker>();
|
|
||||||
const dispatcher = new ResultDispatcher();
|
|
||||||
|
|
||||||
let checkAge = 0;
|
|
||||||
const untilServiceWorkerAlive = async (
|
|
||||||
worker: ServiceWorker,
|
|
||||||
expectedAge: number,
|
|
||||||
) => {
|
|
||||||
const [call, ret] = dispatcher.createTypedCall("ping", undefined);
|
|
||||||
worker.postMessage(await call);
|
|
||||||
const result = await ret;
|
|
||||||
console.assert(!result.error, result);
|
|
||||||
if (expectedAge === checkAge) {
|
|
||||||
setServiceWorker(worker);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
makeEventListener(window, "message", (event: MessageEvent<JSONRPC>) => {
|
|
||||||
if (isJSONRPCResult(event.data)) {
|
|
||||||
dispatcher.dispatch(event.data.id, event.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
needRefresh: [needRefresh],
|
|
||||||
offlineReady: [offlineReady],
|
|
||||||
} = useRegisterSW({
|
|
||||||
onRegisteredSW(scriptUrl, reg) {
|
|
||||||
console.info("service worker is registered from %s", scriptUrl);
|
|
||||||
const active = reg?.active;
|
|
||||||
if (!active) {
|
|
||||||
console.warn("No service is in activating or activated");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
untilServiceWorkerAlive(active, checkAge++);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const clients = createMemo(() => {
|
const clients = createMemo(() => {
|
||||||
return accts().map((x) => ({
|
return accts().map((x) => ({
|
||||||
|
@ -149,15 +103,7 @@ const App: Component = () => {
|
||||||
<ThemeProvider theme={theme()}>
|
<ThemeProvider theme={theme()}>
|
||||||
<DateFnScope>
|
<DateFnScope>
|
||||||
<ClientProvider value={clients}>
|
<ClientProvider value={clients}>
|
||||||
<ServiceWorkerProvider
|
<Routing />
|
||||||
value={{
|
|
||||||
needRefresh,
|
|
||||||
offlineReady,
|
|
||||||
serviceWorker,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Routing />
|
|
||||||
</ServiceWorkerProvider>
|
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</DateFnScope>
|
</DateFnScope>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -1,37 +1,12 @@
|
||||||
import { createContext, useContext, type Accessor } from "solid-js";
|
|
||||||
import { useRegisterSW } from "virtual:pwa-register/solid";
|
|
||||||
|
|
||||||
export function isiOS() {
|
export function isiOS() {
|
||||||
return (
|
return [
|
||||||
[
|
'iPad Simulator',
|
||||||
"iPad Simulator",
|
'iPhone Simulator',
|
||||||
"iPhone Simulator",
|
'iPod Simulator',
|
||||||
"iPod Simulator",
|
'iPad',
|
||||||
"iPad",
|
'iPhone',
|
||||||
"iPhone",
|
'iPod'
|
||||||
"iPod",
|
].includes(navigator.platform)
|
||||||
].includes(navigator.platform) ||
|
// iPad on iOS 13 detection
|
||||||
// iPad on iOS 13 detection
|
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
||||||
(navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ServiceWorkerService = {
|
|
||||||
needRefresh: Accessor<boolean>;
|
|
||||||
offlineReady: Accessor<boolean>;
|
|
||||||
serviceWorker: Accessor<ServiceWorker | undefined>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ServiceWorkerContext = /* @__PURE__ */ createContext<
|
|
||||||
ServiceWorkerService
|
|
||||||
>(({
|
|
||||||
needRefresh: () => false,
|
|
||||||
offlineReady: () => false,
|
|
||||||
serviceWorker: () => undefined
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ServiceWorkerProvider = ServiceWorkerContext.Provider;
|
|
||||||
|
|
||||||
export function useServiceWorker(): ServiceWorkerService {
|
|
||||||
return useContext(ServiceWorkerContext);
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
|
|
||||||
import { clientsClaim } from "workbox-core";
|
|
||||||
import type { AnyCall } from "./services";
|
|
||||||
|
|
||||||
function checkServiceWorker(
|
|
||||||
self: WorkerGlobalScope,
|
|
||||||
): self is ServiceWorkerGlobalScope {
|
|
||||||
return !!(self as unknown as ServiceWorkerGlobalScope).registration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkServiceWorker(self)) {
|
|
||||||
cleanupOutdatedCaches();
|
|
||||||
precacheAndRoute(self.__WB_MANIFEST, {
|
|
||||||
cleanURLs: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// auto update
|
|
||||||
self.skipWaiting();
|
|
||||||
clientsClaim();
|
|
||||||
} else {
|
|
||||||
throw new TypeError("This entry point must be run in a service worker");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addEventListener("message", (event: MessageEvent<AnyCall>) => {
|
|
||||||
if (event.data.method === "ping") {
|
|
||||||
event.source.postMessage({id: event.data.id, jsonrpc: "2.0", result: undefined})
|
|
||||||
} else {
|
|
||||||
event.source.postMessage({
|
|
||||||
error: {
|
|
||||||
code: -32601,
|
|
||||||
message: "Method not found"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,177 +0,0 @@
|
||||||
export type JSONRPC = {
|
|
||||||
jsonrpc: "2.0";
|
|
||||||
id?: string | number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Call<T = undefined> = JSONRPC & {
|
|
||||||
method: string;
|
|
||||||
params: T;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RemoteError<E = undefined> = { data: E } & (
|
|
||||||
| {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
code: -32700;
|
|
||||||
message: "Parse Error";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
code: -32600;
|
|
||||||
message: "Invalid Request";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
code: -32601;
|
|
||||||
message: "Method not found";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
code: -32602;
|
|
||||||
message: "Invalid params";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
code: -32603;
|
|
||||||
message: "Internal error";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export type Result<T, E> = JSONRPC & { id: string | number } & (
|
|
||||||
| {
|
|
||||||
result: T;
|
|
||||||
error: undefined;
|
|
||||||
}
|
|
||||||
| { error: RemoteError<E>; result: undefined }
|
|
||||||
);
|
|
||||||
|
|
||||||
export function isJSONRPCResult(
|
|
||||||
object: Record<string, unknown>,
|
|
||||||
): object is Result<unknown, unknown> {
|
|
||||||
return object["jsonrpc"] === "2.0" && object["id"] && !object["method"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isJSONRPCCall(
|
|
||||||
object: Record<string, unknown>,
|
|
||||||
): object is Call<unknown> {
|
|
||||||
return object["jsonrpc"] === "2.0" && !!object["method"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ResultDispatcher {
|
|
||||||
private map: Map<
|
|
||||||
number | string,
|
|
||||||
((value: Result<unknown, unknown>) => void) | true // `true` = a call is generated, but the promise not created
|
|
||||||
>;
|
|
||||||
private nextId: number = Number.MIN_SAFE_INTEGER;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.map = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
private rollId() {
|
|
||||||
let id = 0;
|
|
||||||
while (this.map.get((id = this.nextId++))) {
|
|
||||||
if (this.nextId >= Number.MAX_SAFE_INTEGER) {
|
|
||||||
this.nextId = Number.MIN_SAFE_INTEGER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
createCall<T>(
|
|
||||||
method: string,
|
|
||||||
params: T,
|
|
||||||
): [Promise<Call<T>>, Promise<Result<unknown, unknown>>] {
|
|
||||||
const id = this.rollId();
|
|
||||||
const p = new Promise<Result<unknown, unknown>>((resolve) =>
|
|
||||||
this.map.set(id, resolve),
|
|
||||||
);
|
|
||||||
this.map.set(id, true);
|
|
||||||
const call: Call<T> = {
|
|
||||||
jsonrpc: "2.0",
|
|
||||||
id,
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
|
||||||
new Promise((resolve) => {
|
|
||||||
const waitUntilTheIdSet = () => {
|
|
||||||
// We must do this check to make sure the id is set before the call made.
|
|
||||||
// or the dispatching may lost the callback
|
|
||||||
if (this.map.get(id)) {
|
|
||||||
resolve(call);
|
|
||||||
} else {
|
|
||||||
setTimeout(waitUntilTheIdSet, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
waitUntilTheIdSet();
|
|
||||||
}),
|
|
||||||
p,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(id: string | number, message: Result<unknown, unknown>) {
|
|
||||||
{
|
|
||||||
const callback = this.map.get(id);
|
|
||||||
if (!callback) return;
|
|
||||||
if (typeof callback !== "boolean") {
|
|
||||||
callback(message);
|
|
||||||
this.map.delete(id);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
let retried = 0;
|
|
||||||
|
|
||||||
const checkAndDispatch = () => {
|
|
||||||
const callback = this.map.get(id);
|
|
||||||
if (typeof callback !== "boolean") {
|
|
||||||
callback(message);
|
|
||||||
this.map.delete(id);
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(checkAndDispatch, 0);
|
|
||||||
if (++retried > 3) {
|
|
||||||
console.warn(
|
|
||||||
`retried ${retried} time(s) but the callback is still disappeared, id is "${id}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// start the loop
|
|
||||||
checkAndDispatch();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createTypedCall<
|
|
||||||
K extends keyof RequestParams,
|
|
||||||
P extends RequestParams[K],
|
|
||||||
R extends ResponseResults[K],
|
|
||||||
E extends ResponseErrorDatas[K],
|
|
||||||
>(method: K, params: P): [Promise<Call<P>>, Promise<Result<R, E>>] {
|
|
||||||
return this.createCall(method, params) as [
|
|
||||||
Promise<Call<P>>,
|
|
||||||
Promise<Result<R, E>>,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestParams {
|
|
||||||
ping: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResponseResults {
|
|
||||||
ping: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResponseErrorDatas {
|
|
||||||
[key: string]: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AnyCall<K extends keyof RequestParams = keyof RequestParams> =
|
|
||||||
JSONRPC & {
|
|
||||||
method: K;
|
|
||||||
params: RequestParams[K];
|
|
||||||
};
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["ESNext", "WebWorker"],
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -45,7 +45,6 @@ import {
|
||||||
} from "../platform/i18n.jsx";
|
} from "../platform/i18n.jsx";
|
||||||
import { type Template } from "@solid-primitives/i18n";
|
import { type Template } from "@solid-primitives/i18n";
|
||||||
import BottomSheet from "../material/BottomSheet.jsx";
|
import BottomSheet from "../material/BottomSheet.jsx";
|
||||||
import { useServiceWorker } from "../platform/host.js";
|
|
||||||
|
|
||||||
type Strings = {
|
type Strings = {
|
||||||
["lang.auto"]: Template<{ detected: string }>;
|
["lang.auto"]: Template<{ detected: string }>;
|
||||||
|
@ -61,7 +60,9 @@ const Settings: ParentComponent = (props) => {
|
||||||
);
|
);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const settings$ = useStore($settings);
|
const settings$ = useStore($settings);
|
||||||
const { needRefresh, offlineReady } = useServiceWorker();
|
const {
|
||||||
|
needRefresh: [needRefresh],
|
||||||
|
} = useRegisterSW();
|
||||||
const dateFnLocale = useDateFnLocale();
|
const dateFnLocale = useDateFnLocale();
|
||||||
|
|
||||||
const [profiles] = useSignedInProfiles();
|
const [profiles] = useSignedInProfiles();
|
||||||
|
@ -235,16 +236,6 @@ const Settings: ParentComponent = (props) => {
|
||||||
</Show>
|
</Show>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ListItem>
|
|
||||||
<ListItemText secondary={
|
|
||||||
offlineReady()
|
|
||||||
? t("availability.offline")
|
|
||||||
: t("availability.online")
|
|
||||||
}>
|
|
||||||
{t("availability")}
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
<Divider />
|
|
||||||
</li>
|
</li>
|
||||||
</List>
|
</List>
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
|
|
|
@ -32,9 +32,5 @@
|
||||||
"motions.gifs": "GIFs",
|
"motions.gifs": "GIFs",
|
||||||
"motions.gifs.autoplay": "Auto-play GIFs",
|
"motions.gifs.autoplay": "Auto-play GIFs",
|
||||||
"motions.vids": "Videos",
|
"motions.vids": "Videos",
|
||||||
"motions.vids.autoplay": "Auto-play Videos",
|
"motions.vids.autoplay": "Auto-play Videos"
|
||||||
|
|
||||||
"availability": "Availability",
|
|
||||||
"availability.offline": "Offline ready",
|
|
||||||
"availability.online": "Online only"
|
|
||||||
}
|
}
|
|
@ -32,9 +32,5 @@
|
||||||
"motions.gifs": "动图",
|
"motions.gifs": "动图",
|
||||||
"motions.gifs.autoplay": "自动播放动图",
|
"motions.gifs.autoplay": "自动播放动图",
|
||||||
"motions.vids": "视频",
|
"motions.vids": "视频",
|
||||||
"motions.vids.autoplay": "自动播放视频",
|
"motions.vids.autoplay": "自动播放视频"
|
||||||
|
|
||||||
"availability": "离线可用程度",
|
|
||||||
"availability.offline": "可以离线使用",
|
|
||||||
"availability.online": "需要联网使用"
|
|
||||||
}
|
}
|
|
@ -24,7 +24,6 @@ import { css } from "solid-styled";
|
||||||
import { vibrate } from "../platform/hardware";
|
import { vibrate } from "../platform/hardware";
|
||||||
import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
|
import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
|
||||||
import TootComposer from "./TootComposer";
|
import TootComposer from "./TootComposer";
|
||||||
import { useDocumentTitle } from "../utils";
|
|
||||||
|
|
||||||
let cachedEntry: [string, mastodon.v1.Status] | undefined;
|
let cachedEntry: [string, mastodon.v1.Status] | undefined;
|
||||||
|
|
||||||
|
@ -107,14 +106,8 @@ const TootBottomSheet: Component = (props) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useDocumentTitle(() => {
|
|
||||||
const t = toot()?.reblog ?? toot()
|
|
||||||
const name = t?.account.displayName ?? "Someone"
|
|
||||||
return `${name}'s toot`
|
|
||||||
})
|
|
||||||
|
|
||||||
const tootDisplayName = () => {
|
const tootDisplayName = () => {
|
||||||
const t = toot()?.reblog ?? toot();
|
const t = toot();
|
||||||
if (t) {
|
if (t) {
|
||||||
return resolveCustomEmoji(t.account.displayName, t.account.emojis);
|
return resolveCustomEmoji(t.account.displayName, t.account.emojis);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
import {
|
import { createRenderEffect, createSignal, onCleanup } from "solid-js";
|
||||||
createRenderEffect,
|
|
||||||
onCleanup,
|
|
||||||
type Accessor,
|
|
||||||
} from "solid-js";
|
|
||||||
|
|
||||||
export function useDocumentTitle(newTitle?: string | Accessor<string>) {
|
export function useDocumentTitle(newTitle?: string) {
|
||||||
const capturedTitle = document.title;
|
const capturedTitle = document.title;
|
||||||
|
const [title, setTitle] = createSignal(newTitle ?? capturedTitle);
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
if (newTitle)
|
document.title = title();
|
||||||
document.title = typeof newTitle === "string" ? newTitle : newTitle();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
document.title = capturedTitle;
|
document.title = capturedTitle;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (x: ((x: string) => string) | string) =>
|
return setTitle;
|
||||||
(document.title = typeof x === "string" ? x : x(document.title));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeClass(c1: string | undefined, c2: string | undefined) {
|
export function mergeClass(c1: string | undefined, c2: string | undefined) {
|
||||||
|
|
|
@ -16,16 +16,10 @@ export default defineConfig(({ mode }) => ({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
strategies: "injectManifest",
|
|
||||||
registerType: "autoUpdate",
|
registerType: "autoUpdate",
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: mode === "staging" || mode === "dev",
|
enabled: mode === "staging",
|
||||||
},
|
},
|
||||||
srcDir: "src/serviceworker",
|
|
||||||
filename: "main.ts",
|
|
||||||
manifest: {
|
|
||||||
theme_color: "#673ab7"
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
version(),
|
version(),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue