Skip to content

Commit

Permalink
feat(all): embed options
Browse files Browse the repository at this point in the history
  • Loading branch information
ikkz committed Jan 10, 2025
1 parent f796da5 commit 6270616
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/young-penguins-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'anki-templates': minor
---

feat(all): embed options (嵌入选项)
11 changes: 11 additions & 0 deletions build/rollup.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ export async function rollupOptions(config) {
fileName: `front.html`,
template({ files }) {
let frontHtml = '';
frontHtml += `<script>
window.atDefaultOptions =
/* options begin */
{
"at:test:test": "test"
}
/* options end */
</script>
`;
frontHtml += `<div data-at-version="${packageJson.version}" id="at-root"></div>`;
frontHtml += `<style>${files?.css?.map(({ source }) => source).join('')}</style>`;
frontHtml += `
Expand Down
1 change: 1 addition & 0 deletions src/hooks/use-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum Page {
Index = 'index',
Settings = 'settings',
Tools = 'tools',
Options = 'options',
}

export type PageMap = Partial<Record<Page, FC>>;
Expand Down
2 changes: 2 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import OptionsPage from './options';
import SettingsPage from './settings';
import ToolsPage from './tools';
import { Page, PageMap } from '@/hooks/use-page';

export const DEFAULT_PAGES: PageMap = {
[Page.Settings]: SettingsPage,
[Page.Tools]: ToolsPage,
[Page.Options]: OptionsPage,
};
36 changes: 36 additions & 0 deletions src/pages/options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Block } from '@/components/block';
import { useNavigate, Page } from '@/hooks/use-page';
import * as t from 'at/i18n';
import { id } from 'at/options';
import { useMemo } from 'react';

export default () => {
const navigate = useNavigate();
const html = useMemo(() => {
const options = Object.fromEntries(
Object.keys(localStorage)
.filter(
(key) =>
(key.startsWith('at:_global:') || key.startsWith(`at:${id}:`)) &&
!!localStorage.getItem(key),
)
.map((key) => [key, localStorage.getItem(key)]),
);
return `${t.optionsHelp}<pre>${JSON.stringify(options, undefined, 2)}</pre>`;
}, []);

return (
<>
<Block
name={t.optionsPage}
action={t.back}
onAction={() => navigate(Page.Settings)}
>
<div
className="prose prose-sm prose-neutral dark:prose-invert"
dangerouslySetInnerHTML={{ __html: html }}
/>
</Block>
</>
);
};
4 changes: 4 additions & 0 deletions src/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export default () => {
action={t.back}
onAction={() => navigate(Page.Index)}
>
<div className="text-gray-500 dark:text-gray-400 text-sm mb-3">
{t.optionsHint}
<Button onClick={() => navigate(Page.Options)}>{t.optionsPage}</Button>
</div>
<div className="flex flex-col gap-4">
<OptionList />
</div>
Expand Down
65 changes: 49 additions & 16 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ import { isAnkiDroid } from './bridge';
import { getAnkiStorage } from 'anki-storage';
import { id } from 'at/options';
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
import { doNothing } from 'remeda';

declare global {
interface Window {
atDefaultOptions?: Record<string, string>;
}
}

const { atDefaultOptions } = window;

// set default options to localStorage when this template type is displayed first time
// users can still change the options, which will take effects during this Anki execution
const initKey = `at:init:${id}`;
if (atDefaultOptions && !sessionStorage.getItem(initKey)) {
Object.entries(atDefaultOptions).forEach(([key, value]) => {
localStorage.setItem(key, value);
});
sessionStorage.setItem(initKey, 'true');
}
const defaultOptionKeys = Object.keys(atDefaultOptions || {});

function isDefaultOptionKey(key: string) {
return defaultOptionKeys.includes(key);
}

function createAnkiDroidStorage() {
const as: ReturnType<typeof getAnkiStorage> = new Promise((resolve) => {
Expand All @@ -13,33 +37,42 @@ function createAnkiDroidStorage() {

return createJSONStorage<any>(() => ({
getItem(key) {
as.then((api) => {
api.localStorage.getItem(key).then((value) => {
if (value !== localStorage.getItem(key)) {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
if (!isDefaultOptionKey(key)) {
as.then((api) => {
api.localStorage.getItem(key).then((value) => {
if (value !== localStorage.getItem(key)) {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
updaters.get(key)?.forEach((fn) => fn(value));
}
updaters.get(key)?.forEach((fn) => fn(value));
}
});
});
});
}
return localStorage.getItem(key);
},
setItem(key, newValue) {
localStorage.setItem(key, newValue);
as.then((api) => {
api.localStorage.setItem(key, newValue);
});
if (!isDefaultOptionKey(key)) {
as.then((api) => {
api.localStorage.setItem(key, newValue);
});
}
},
removeItem(key) {
localStorage.removeItem(key);
as.then((api) => {
api.localStorage.removeItem(key);
});
if (!isDefaultOptionKey(key)) {
as.then((api) => {
api.localStorage.removeItem(key);
});
}
},
subscribe(key, callback) {
if (isDefaultOptionKey(key)) {
return doNothing;
}
if (updaters.has(key)) {
updaters.get(key)?.push(callback);
} else {
Expand Down
2 changes: 1 addition & 1 deletion translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"question": "Question",
"singleAnswer": "One answer",
"multipleAnswer": "Multiple answers",
"templateSetting": "Template settings",
"templateSetting": "Settings",
"back": "Back",
"missingAnswer": "Missing answer",
"missingOptions": "Missing options",
Expand Down
7 changes: 5 additions & 2 deletions translations/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"question": "题目",
"singleAnswer": "单选",
"multipleAnswer": "多选",
"templateSetting": "模板设置",
"templateSetting": "设置",
"back": "返回",
"missingAnswer": "缺少答案",
"missingOptions": "缺少选项",
Expand Down Expand Up @@ -49,5 +49,8 @@
"translate": "翻译",
"search": "搜索",
"explainFollowing": "解释以下内容: ",
"toolHelp": "<p>每一个工具都需要设置 url。以谷歌搜索为例</p><pre><code>https://www.google.com/search?q={q}</code></pre><p>在选中文本后点击对应的工具时,链接中的 <code>{q}</code> 将会被替换为您选择的文本,然后将自动跳转到替换后的链接。</p><p>前置文本和后置文本是指在选中的文本前后添加对应的文本,然后作为 <code>{q}</code> 来执行替换。</p>"
"toolHelp": "<p>每一个工具都需要设置 url。以谷歌搜索为例</p><pre><code>https://www.google.com/search?q={q}</code></pre><p>在选中文本后点击对应的工具时,链接中的 <code>{q}</code> 将会被替换为您选择的文本,然后将自动跳转到替换后的链接。</p><p>前置文本和后置文本是指在选中的文本前后添加对应的文本,然后作为 <code>{q}</code> 来执行替换。</p>",
"optionsHint": "如果在重新打开Anki之后丢失了以前的设置,或者希望以下设置在设备之间同步,请前往",
"optionsPage": "选项配置",
"optionsHelp": "<p>以下内容可以选中复制,详细步骤请查看 <a target=\"_blank\" href=\"https://github.com/ikkz/anki-template/blob/main/docs/embed-options.md\">这里</a></p>"
}

0 comments on commit 6270616

Please sign in to comment.