mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
feat: import config from old domain
This commit is contained in:
@@ -16,6 +16,7 @@ import LazyLoad from '@/components/utils/LazyLoad';
|
|||||||
import usePopup from '@/components/utils/usePopup';
|
import usePopup from '@/components/utils/usePopup';
|
||||||
import { colorThemes } from '@/core/colorThemes';
|
import { colorThemes } from '@/core/colorThemes';
|
||||||
import useConfig, { type Config } from '@/core/useConfig';
|
import useConfig, { type Config } from '@/core/useConfig';
|
||||||
|
import { useOldConfig } from '@/hooks/useInterWindow';
|
||||||
import useModalState from '@/hooks/useModalState';
|
import useModalState from '@/hooks/useModalState';
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import usePubkey from '@/nostr/usePubkey';
|
import usePubkey from '@/nostr/usePubkey';
|
||||||
@@ -634,6 +635,7 @@ const OtherConfig = () => {
|
|||||||
const ConfigUI = (props: ConfigProps) => {
|
const ConfigUI = (props: ConfigProps) => {
|
||||||
const i18n = useTranslation();
|
const i18n = useTranslation();
|
||||||
const [menuIndex, setMenuIndex] = createSignal<number | null>(null);
|
const [menuIndex, setMenuIndex] = createSignal<number | null>(null);
|
||||||
|
const { canImport, importConfig } = useOldConfig();
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{
|
{
|
||||||
@@ -690,6 +692,19 @@ const ConfigUI = (props: ConfigProps) => {
|
|||||||
fallback={
|
fallback={
|
||||||
<>
|
<>
|
||||||
<h2 class="flex-1 text-center text-lg font-bold">{i18n()('config.config')}</h2>
|
<h2 class="flex-1 text-center text-lg font-bold">{i18n()('config.config')}</h2>
|
||||||
|
<Show when={canImport()}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded bg-primary p-2 text-primary-fg"
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm(i18n()('config.confirmImportOldDomainConfig'))) {
|
||||||
|
importConfig();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n()('config.importOldDomainConfig')}
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
<ul class="flex flex-col">
|
<ul class="flex flex-col">
|
||||||
<For each={menu}>
|
<For each={menu}>
|
||||||
{(menuItem, i) => (
|
{(menuItem, i) => (
|
||||||
|
|||||||
123
src/hooks/useInterWindow.tsx
Normal file
123
src/hooks/useInterWindow.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { createSignal, onMount, onCleanup, observable } from 'solid-js';
|
||||||
|
|
||||||
|
import useConfig from '@/core/useConfig';
|
||||||
|
import {
|
||||||
|
type InterWindowRequest,
|
||||||
|
type InterWindowRequestWithId,
|
||||||
|
type InterWindowResponseWithId,
|
||||||
|
} from '@/interWindow';
|
||||||
|
|
||||||
|
const useInterWindow = (url: string) => {
|
||||||
|
let iframeRef: HTMLIFrameElement | undefined;
|
||||||
|
const [loaded, setLoaded] = createSignal(false);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
iframeRef = (<iframe title="interwindow" class="hidden" src={url} />) as HTMLIFrameElement;
|
||||||
|
document.body.appendChild(iframeRef);
|
||||||
|
iframeRef.addEventListener('load', () => setLoaded(true));
|
||||||
|
onCleanup(() => {
|
||||||
|
iframeRef?.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitLoaded = (): Promise<void> => {
|
||||||
|
if (loaded()) return Promise.resolve();
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
observable(loaded).subscribe((v) => {
|
||||||
|
if (v) resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const listen = async (
|
||||||
|
expectedRequestId: number,
|
||||||
|
timeout: number = 2000,
|
||||||
|
): Promise<InterWindowResponseWithId> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const listener = (event: MessageEvent) => {
|
||||||
|
console.log('message received', event);
|
||||||
|
if (event.origin !== new URL(url).origin) return;
|
||||||
|
if (typeof event.data !== 'string') return;
|
||||||
|
|
||||||
|
const data = JSON.parse(event.data) as InterWindowResponseWithId;
|
||||||
|
|
||||||
|
if (data.requestId !== expectedRequestId) return;
|
||||||
|
|
||||||
|
window.removeEventListener('message', listener);
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('timeout'));
|
||||||
|
window.removeEventListener('message', listener);
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
window.addEventListener('message', listener, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = async (message: InterWindowRequest) => {
|
||||||
|
const requestId = Math.random();
|
||||||
|
const withId = { ...message, requestId } satisfies InterWindowRequestWithId;
|
||||||
|
const messageStr = JSON.stringify(withId);
|
||||||
|
|
||||||
|
await waitLoaded();
|
||||||
|
const promise = listen(requestId);
|
||||||
|
if (iframeRef == null) {
|
||||||
|
throw new Error('iframeRef is null');
|
||||||
|
}
|
||||||
|
if (!loaded()) {
|
||||||
|
throw new Error('iframeRef is not loaded');
|
||||||
|
}
|
||||||
|
iframeRef.contentWindow?.postMessage(messageStr, new URL(url).origin);
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConfig = () => request({ type: 'GET_CONFIG' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
loaded,
|
||||||
|
request,
|
||||||
|
getConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOldConfig = () => {
|
||||||
|
const url = 'https://syusui-s.github.io/rabbit/transfer-config.html';
|
||||||
|
const { getConfig } = useInterWindow(url);
|
||||||
|
const { setConfig } = useConfig();
|
||||||
|
|
||||||
|
const [oldConfig, setOldConfig] = createSignal<string | undefined>();
|
||||||
|
|
||||||
|
const canImport = () => {
|
||||||
|
const c = oldConfig();
|
||||||
|
return c != null && c.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const importConfig = () => {
|
||||||
|
const c = oldConfig();
|
||||||
|
if (c != null && c.length > 0) {
|
||||||
|
setConfig(JSON.parse(c));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
getConfig()
|
||||||
|
.then((obtainedConfig) => {
|
||||||
|
if (typeof obtainedConfig.payload === 'string' && obtainedConfig.payload.length > 0) {
|
||||||
|
setOldConfig(obtainedConfig.payload);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
canImport,
|
||||||
|
importConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInterWindow;
|
||||||
38
src/interWindow.ts
Normal file
38
src/interWindow.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export type InterWindowRequest = {
|
||||||
|
type: 'GET_CONFIG';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterWindowRequestWithId = InterWindowRequest & {
|
||||||
|
requestId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterWindowResponse = {
|
||||||
|
type: 'OK' | 'BAD_REQUEST' | 'NOT_FOUND' | 'INTERNAL_PEER_ERROR';
|
||||||
|
payload?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterWindowResponseWithId = InterWindowResponse & {
|
||||||
|
requestId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RequestHandler = (request: InterWindowRequestWithId) => InterWindowResponse;
|
||||||
|
|
||||||
|
export const ok = (payload?: string): InterWindowResponse => ({
|
||||||
|
type: 'OK',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const badRequest = (payload?: string): InterWindowResponse => ({
|
||||||
|
type: 'BAD_REQUEST',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notFound = (payload?: string): InterWindowResponse => ({
|
||||||
|
type: 'NOT_FOUND',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const internalPeerError = (payload?: string): InterWindowResponse => ({
|
||||||
|
type: 'INTERNAL_PEER_ERROR',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
@@ -125,6 +125,8 @@ export default {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
config: 'Settings',
|
config: 'Settings',
|
||||||
|
importOldDomainConfig: 'Import config from the old domain',
|
||||||
|
confirmImportOldDomainConfig: 'Import? (The config will be overwritten)',
|
||||||
profile: {
|
profile: {
|
||||||
profile: 'Profile',
|
profile: 'Profile',
|
||||||
openProfile: 'Open',
|
openProfile: 'Open',
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ export default {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
config: '設定',
|
config: '設定',
|
||||||
|
importOldDomainConfig: '古いドメインから設定をインポート',
|
||||||
|
confirmImportOldDomainConfig: 'インポートしますか?(現在の設定は上書きされます)',
|
||||||
profile: {
|
profile: {
|
||||||
profile: 'プロフィール',
|
profile: 'プロフィール',
|
||||||
openProfile: '開く',
|
openProfile: '開く',
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { createSignal, onMount, Switch, Match, type Component } from 'solid-js';
|
|||||||
|
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
|
|
||||||
|
import useConfig from '@/core/useConfig';
|
||||||
|
import { useOldConfig } from '@/hooks/useInterWindow';
|
||||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import resolveAsset from '@/utils/resolveAsset';
|
import resolveAsset from '@/utils/resolveAsset';
|
||||||
@@ -36,13 +38,24 @@ const Hello: Component = () => {
|
|||||||
const signerStatus = useSignerStatus();
|
const signerStatus = useSignerStatus();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { persistStatus, loggedIn } = usePersistStatus();
|
const { persistStatus, loggedIn } = usePersistStatus();
|
||||||
|
const { config } = useConfig();
|
||||||
|
const { canImport, importConfig } = useOldConfig();
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
|
if (
|
||||||
|
config().columns.length === 0 &&
|
||||||
|
canImport() &&
|
||||||
|
window.confirm('import config from old domain?')
|
||||||
|
) {
|
||||||
|
importConfig();
|
||||||
|
}
|
||||||
|
|
||||||
loggedIn();
|
loggedIn();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
console.log();
|
||||||
if (persistStatus().loggedIn) {
|
if (persistStatus().loggedIn) {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
|
|||||||
9
transfer-config.html
Normal file
9
transfer-config.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Transfer Config</title>
|
||||||
|
<script src="transfer-config.ts" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
53
transfer-config.ts
Normal file
53
transfer-config.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
ok,
|
||||||
|
notFound,
|
||||||
|
internalPeerError,
|
||||||
|
type InterWindowRequestWithId,
|
||||||
|
type RequestHandler,
|
||||||
|
} from '@/interWindow';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const acceptableOrigins = [
|
||||||
|
window.location.origin,
|
||||||
|
'http://localhost:3000',
|
||||||
|
'http://localhost:12345',
|
||||||
|
'https://rabbit.syusui.net',
|
||||||
|
];
|
||||||
|
|
||||||
|
const rawMessageHandler = (handler: RequestHandler) => (event: MessageEvent) => {
|
||||||
|
console.log('transfer-config: received request', event.data, event.origin);
|
||||||
|
if (!acceptableOrigins.includes(event.origin)) return;
|
||||||
|
|
||||||
|
const { origin, source } = event;
|
||||||
|
if (typeof event.data !== 'string') return;
|
||||||
|
const request = JSON.parse(event.data) as InterWindowRequestWithId;
|
||||||
|
|
||||||
|
let responseObj;
|
||||||
|
try {
|
||||||
|
responseObj = handler(request);
|
||||||
|
} catch (e) {
|
||||||
|
responseObj = internalPeerError(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = JSON.stringify({ requestId: request.requestId, ...responseObj });
|
||||||
|
// @ts-expect-error postMessage
|
||||||
|
source?.postMessage(response, origin);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'message',
|
||||||
|
rawMessageHandler((request) => {
|
||||||
|
switch (request.type) {
|
||||||
|
case 'GET_CONFIG': {
|
||||||
|
const value = window.localStorage.getItem('RabbitConfig') ?? '';
|
||||||
|
return ok(value);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('transfer-config: mounted');
|
||||||
|
})();
|
||||||
@@ -14,6 +14,12 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: './index.html',
|
||||||
|
transferConfig: './transfer-config.html',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user