From 58240ff5205a4d0544e85b28a262f7ae660bd3e6 Mon Sep 17 00:00:00 2001 From: polv Date: Wed, 15 Jul 2026 19:00:32 +0700 Subject: [PATCH] add supabase editor --- docs/.vitepress/config.mts | 1 + .../.vitepress/theme/components/VocabQuiz.vue | 80 +++++++++++++++++-- docs/.vitepress/theme/index.ts | 6 ++ docs/.vitepress/theme/supabase.ts | 27 +++++++ package.json | 1 + pnpm-lock.yaml | 73 +++++++++++++++++ 6 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 docs/.vitepress/theme/supabase.ts diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b882516..7c64be9 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -65,6 +65,7 @@ export default defineConfig({ }, }, vite: { + envDir: process.cwd(), ssr: { noExternal: [], external: ["pdfjs-dist"], // don't bundle it for SSR — leave it browser-only diff --git a/docs/.vitepress/theme/components/VocabQuiz.vue b/docs/.vitepress/theme/components/VocabQuiz.vue index f479180..a0b3342 100644 --- a/docs/.vitepress/theme/components/VocabQuiz.vue +++ b/docs/.vitepress/theme/components/VocabQuiz.vue @@ -100,6 +100,7 @@ import { watch, } from "vue"; import SentenceAudio, { IVoc } from "./SentenceAudio.vue"; +import { supabase } from "../supabase.ts"; const { fmKey = "vocabulary" } = defineProps<{ fmKey?: string; @@ -131,7 +132,7 @@ const example = computed(() => { const MAX_VOC = 5; -const { frontmatter } = useData(); +const { frontmatter, page } = useData(); const vocabulary: Record = {}; @@ -148,7 +149,6 @@ for (const fk of fmKey.split(/[, ]+/g)) { } vChosenCats.value = [...vCats.value]; -makeVList(); watch(isKoFirst, () => { makeVList(); @@ -238,8 +238,46 @@ function updateCats(cat: string) { makeVList(); } -function onVContentSave() { - const newV = { ko: v.value.ko, ...JSON.parse(vEditContent.value) }; +async function onVContentSave() { + const oldV = v.value; + const newV = { ko: oldV.ko, ...JSON.parse(vEditContent.value) }; + + let key = ""; + let vKey = oldV.ko; + + Object.entries(vocabulary).map(([k, vs]) => { + const vCount = new Map(); + + vs.map((v, i_vs) => { + let vK = v.ko; + let i = vCount.get(v.ko) || 0; + if (i) { + vK = vK + `(${i})`; + } + vCount.set(v.ko, i + 1); + + if (oldV.ko === v.ko && oldV.en === v.en) { + key = k; + vKey = vK; + + vocabulary[k][i_vs] = newV; + } + }); + }); + if (supabase && key) { + const user_id = await supabase.auth.getUser().then((r) => r.data.user?.id); + + await supabase.from("quiz").upsert( + { + url: page.value.filePath, + key, + v: vKey, + data: newV, + user_id, + }, + { onConflict: "url,key,v,user_id" }, + ); + } const newVList = [...vList.value]; newVList[vIndex.value] = newV; @@ -259,8 +297,40 @@ function onModalClickOutside(ev: MouseEvent) { } } -onMounted(() => { +onMounted(async () => { document.addEventListener("click", onModalClickOutside); + + if (supabase) { + await supabase + .from("quiz") + .select("v,data") + .eq("url", page.value.filePath) + .then((r) => { + if (!r.data?.length) return; + + const replacement = new Map(); + for (const { v, data } of r.data) { + replacement.set(v, data); + } + + Object.entries(vocabulary).map(([k, vs]) => { + const vCount = new Map(); + + vocabulary[k] = vs.map((v) => { + let vKey = v.ko; + let i = vCount.get(v.ko) || 0; + if (i) { + vKey = vKey + `(${i})`; + } + vCount.set(v.ko, i + 1); + + return replacement.get(vKey) || v; + }); + }); + }); + } + + makeVList(); }); onBeforeUnmount(() => { diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index c467385..7e4de22 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -14,6 +14,7 @@ import VocabQuiz from "./components/VocabQuiz.vue"; import YouTube from "./components/YouTube.vue"; import type { Theme } from "vitepress"; +import { signIn } from "./supabase.ts"; export default { extends: DefaultTheme, Layout: () => { @@ -32,3 +33,8 @@ export default { // app.component("PDF", PDF); }, } satisfies Theme; + +//@ts-ignore +if (import.meta.env.SSR) { + signIn(); +} diff --git a/docs/.vitepress/theme/supabase.ts b/docs/.vitepress/theme/supabase.ts new file mode 100644 index 0000000..6757487 --- /dev/null +++ b/docs/.vitepress/theme/supabase.ts @@ -0,0 +1,27 @@ +import { createClient } from "@supabase/supabase-js"; + +const { + VITE_SUPABASE_URL, + VITE_SUPABASE_PUBLISHABLE_KEY, + VITE_SUPABASE_USER, + VITE_SUPABASE_USER_PASSWORD, +} = + //@ts-ignore + import.meta.env; + +export const supabase = [ + VITE_SUPABASE_URL, + VITE_SUPABASE_PUBLISHABLE_KEY, + VITE_SUPABASE_USER, + VITE_SUPABASE_USER_PASSWORD, +].every((v) => v) + ? createClient(VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY) + : null; + +export async function signIn() { + if (!supabase) return; + await supabase.auth.signInWithPassword({ + email: VITE_SUPABASE_USER, + password: VITE_SUPABASE_USER_PASSWORD, + }); +} diff --git a/package.json b/package.json index ac457a0..cde8c29 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "vue": "^3.5.39" }, "dependencies": { + "@supabase/supabase-js": "^2.110.5", "pdfjs-dist": "^5.7.284", "tauri-plugin-tts-api": "^0.1.11" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8818f95..c96fe89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@supabase/supabase-js': + specifier: ^2.110.5 + version: 2.110.5 pdfjs-dist: specifier: ^5.7.284 version: 5.7.284 @@ -458,6 +461,33 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@supabase/auth-js@2.110.5': + resolution: {integrity: sha512-QSlI5CNeEefHP95/GbeMhNgr8aHEHiXFh8c1IWthYyI9ZzBwEigYzJFCw4Ff+GkL8OK9tArBmGGv6rpg5zLXSw==} + engines: {node: '>=22.0.0'} + + '@supabase/functions-js@2.110.5': + resolution: {integrity: sha512-nuQuoIoEGT8ukrwr6THlY5v50bSMJ2rqti1FFsGyDaC98POLmcFQVFFh23PVPhRsKWUxrFc0MpmEoHKa7CY4mg==} + engines: {node: '>=22.0.0'} + + '@supabase/phoenix@0.4.4': + resolution: {integrity: sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==} + + '@supabase/postgrest-js@2.110.5': + resolution: {integrity: sha512-eObfgBjxLPzFakwMpUmsv8nuNPOhoDhzzN8cwvEvGkxkmuRIZcOCYH9+DRbpKnxh7tgsBZW+KWtaZI2j98b2/g==} + engines: {node: '>=22.0.0'} + + '@supabase/realtime-js@2.110.5': + resolution: {integrity: sha512-VtOZxw5jfrc37KgIfFdp9SOFJjtvJ0qU+gT8CPQjL7cqy4DJlaTXacw5aBBogLZksqAI0YN67qhlDaMKFwfOuw==} + engines: {node: '>=22.0.0'} + + '@supabase/storage-js@2.110.5': + resolution: {integrity: sha512-7GkOZlrYknVGVPyijoDbu5OXGvQ2oQ6dkU0juAkIMPZ9QgtmJ8IPrxe76zGSbmzbaC1GdKw7pqeXEi2HT67sbA==} + engines: {node: '>=22.0.0'} + + '@supabase/supabase-js@2.110.5': + resolution: {integrity: sha512-cAO1Nm+CCogRNVXN93bBkh0vjOdLM5e6J9gB/cHV9Lqni/gEmN2HJFrnn4NI33GYIfmlh4Wbm6siH+XXRgpexA==} + engines: {node: '>=22.0.0'} + '@tauri-apps/api@2.11.1': resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} @@ -730,6 +760,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + iceberg-js@0.8.1: + resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} + engines: {node: '>=20.0.0'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -829,6 +863,9 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -1186,6 +1223,38 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@supabase/auth-js@2.110.5': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.110.5': + dependencies: + tslib: 2.8.1 + + '@supabase/phoenix@0.4.4': {} + + '@supabase/postgrest-js@2.110.5': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.110.5': + dependencies: + '@supabase/phoenix': 0.4.4 + tslib: 2.8.1 + + '@supabase/storage-js@2.110.5': + dependencies: + iceberg-js: 0.8.1 + tslib: 2.8.1 + + '@supabase/supabase-js@2.110.5': + dependencies: + '@supabase/auth-js': 2.110.5 + '@supabase/functions-js': 2.110.5 + '@supabase/postgrest-js': 2.110.5 + '@supabase/realtime-js': 2.110.5 + '@supabase/storage-js': 2.110.5 + '@tauri-apps/api@2.11.1': {} '@tauri-apps/cli-darwin-arm64@2.11.4': @@ -1438,6 +1507,8 @@ snapshots: html-void-elements@3.0.0: {} + iceberg-js@0.8.1: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1577,6 +1648,8 @@ snapshots: trim-lines@3.0.1: {} + tslib@2.8.1: {} + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3