From e8a6e8e39d4bfbbef71683ccab052ff33b47e5eb Mon Sep 17 00:00:00 2001 From: Cobel Date: Thu, 12 Mar 2026 23:13:41 +0900 Subject: [PATCH] studio media update suggestion On branch dev Changes to be committed: modified: web/package-lock.json modified: web/package.json modified: web/src/locale/en/translation.json modified: web/src/locale/ko/translation.json modified: web/src/routes/studio/-media/SubtitleSet.tsx --- web/package-lock.json | 19 ++++++------- web/package.json | 4 +-- web/src/locale/en/translation.json | 15 +++++++--- web/src/locale/ko/translation.json | 15 +++++++--- web/src/routes/studio/-media/SubtitleSet.tsx | 29 ++++++++++++++++---- 5 files changed, 56 insertions(+), 26 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index bec75f7..e19751f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -31,10 +31,10 @@ "axios": "^1.13.6", "cropperjs": "^2.1.0", "date-fns": "^4.1.0", - "dompurify": "^3.3.2", + "dompurify": "^3.3.3", "fflate": "^0.8.2", "firebase": "^12.10.0", - "i18next": "^25.8.17", + "i18next": "^25.8.18", "i18next-browser-languagedetector": "^8.2.1", "korean-regexp": "^1.0.13", "marked": "^17.0.4", @@ -5817,13 +5817,10 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", - "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "license": "(MPL-2.0 OR Apache-2.0)", - "engines": { - "node": ">=20" - }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -6518,9 +6515,9 @@ } }, "node_modules/i18next": { - "version": "25.8.17", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.17.tgz", - "integrity": "sha512-vWtCttyn5bpOK4hWbRAe1ZXkA+Yzcn2OcACT+WJavtfGMcxzkfvXTLMeOU8MUhRmAySKjU4VVuKlo0sSGeBokA==", + "version": "25.8.18", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.18.tgz", + "integrity": "sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==", "funding": [ { "type": "individual", diff --git a/web/package.json b/web/package.json index 8b91882..cffa289 100644 --- a/web/package.json +++ b/web/package.json @@ -40,10 +40,10 @@ "axios": "^1.13.6", "cropperjs": "^2.1.0", "date-fns": "^4.1.0", - "dompurify": "^3.3.2", + "dompurify": "^3.3.3", "fflate": "^0.8.2", "firebase": "^12.10.0", - "i18next": "^25.8.17", + "i18next": "^25.8.18", "i18next-browser-languagedetector": "^8.2.1", "korean-regexp": "^1.0.13", "marked": "^17.0.4", diff --git a/web/src/locale/en/translation.json b/web/src/locale/en/translation.json index 50a2a07..3d4cd3d 100644 --- a/web/src/locale/en/translation.json +++ b/web/src/locale/en/translation.json @@ -18,11 +18,12 @@ "{{count}} reply_other": "{{count}} replies", "{{count}} result found_one": "{{count}} result found", "{{count}} result found_other": "{{count}} results found", - "{{count}} watched history found_one": "{{count}} watched history found", - "{{count}} watched history found_other": "{{count}} watched histories found", + "{{count}} watched public media found_one": "{{count}} watched public media found_one", + "{{count}} watched public media found_other": "{{count}} watched public media found_other", "{{minutes}} minutes": "{{minutes}} minutes", "{{num}} remains": "{{num}} remains", "{{platform}} Site Policies": "{{platform}} Site Policies", + "{{realm}} preview": "{{realm}} preview", "1:1 Inquiry": "1:1 Inquiry", "Account activation": "Account activation", "Accuracy Rate": "Accuracy Rate", @@ -245,6 +246,7 @@ "Get Started {{sessionKind}}": "Get Started {{sessionKind}}", "Getting started": "Getting started", "Getting Started": "Getting Started", + "Go Back": "Go Back", "goal": "goal", "Google Authenticator": "Google Authenticator", "Grade Confirm Due": "Grade Confirm Due", @@ -371,7 +373,7 @@ "No related courses.": "No related courses.", "No similar answer found": "No similar answer found", "No unread notification": "No unread notification", - "No watched media yet.": "No watched media yet.", + "No watched public media yet.": "No watched public media yet.", "Not provided": "Not provided", "Not reviewed / All appeals": "Not reviewed / All appeals", "Notification": "Notification", @@ -390,6 +392,7 @@ "Otp Setup Completed Successfully": "Otp Setup Completed Successfully", "OTP Verification Required": "OTP Verification Required", "Outline": "Outline", + "Page Not Found": "Page Not Found", "Paragraph": "Paragraph", "Pass": "Pass", "Passed": "Passed", @@ -411,6 +414,7 @@ "Plagiarism threshold": "Plagiarism threshold", "Plagiarism threshold is {{threshold}}%. Over this threshold, the submission will be rejected.": "Plagiarism threshold is {{threshold}}%. Over this threshold, the submission will be rejected.", "Please agree to the following policies": "Please agree to the following policies", + "Please check the address and try again.": "Please check the address and try again.", "Please fill out the form below to join.": "Please fill out the form below to join.", "Please review carefully before submitting, as grade appeals become final once submitted.": "Please review carefully before submitting, as grade appeals become final once submitted.", "Please select a rating.": "Please select a rating.", @@ -423,7 +427,6 @@ "Post Point: {{count}}_one": "Post Point: {{count}}", "Post Point: {{count}}_other": "Post Points: {{count}}", "Preview": "Preview", - "Preview mode": "Preview mode", "Preview URL": "Preview URL", "Privacy Policy": "Privacy Policy", "Private": "Private", @@ -467,6 +470,7 @@ "Request email change": "Request email change", "Request password change": "Request password change", "required": "required", + "Required": "Required", "required *": "required *", "Required *": "Required *", "Researcher": "Researcher", @@ -513,6 +517,7 @@ "Skill factors: {{num}}": "Skill factors: {{num}}", "Skills": "Skills", "Solved": "Solved", + "Something went wrong": "Something went wrong", "SSO Error": "SSO Error", "Staffs": "Staffs", "Standard Score": "Standard Score", @@ -550,6 +555,7 @@ "The deadline for participating in the discussion is {{date}}.": "The deadline for participating in the discussion is {{date}}.", "The deadline for submitting the assignment is {{date}}.": "The deadline for submitting the assignment is {{date}}.", "The final grade confirmation due is {{date}}.": "The final grade confirmation due is {{date}}.", + "The page you are looking for does not exist or you do not have permission to access it.": "The page you are looking for does not exist or you do not have permission to access it.", "Third Level": "Third Level", "This action cannot be undone. Are you sure you want to proceed?": "This action cannot be undone. Are you sure you want to proceed?", "This course has no certificates.": "This course has no certificates.", @@ -576,6 +582,7 @@ "Type to search": "Type to search", "Unable to display timer": "Unable to display timer", "Unenroll": "Unenroll", + "Unknown Address": "Unknown Address", "Unlimited": "Unlimited", "Unpublished": "Unpublished", "Update": "Update", diff --git a/web/src/locale/ko/translation.json b/web/src/locale/ko/translation.json index 144cdbb..b5c5cdd 100644 --- a/web/src/locale/ko/translation.json +++ b/web/src/locale/ko/translation.json @@ -9,11 +9,12 @@ "{{count}} post_other": "{{count}} 게시글", "{{count}} Question_other": "{{count}} 문제", "{{count}} reply_other": "{{count}} 답글", - "{{count}} result found_other": "{{count}}개의 결과를 찾았습니다.", - "{{count}} watched history found_other": "{{count}} 시청 기록", + "{{count}} result found_other": "{{count}}개 결과를 찾았습니다.", + "{{count}} watched public media found_other": "{{count}}개 시청된 공개 미디어를 찾았습니다.", "{{minutes}} minutes": "{{minutes}}분", "{{num}} remains": "{{num}} 남음", "{{platform}} Site Policies": "{{platform}} 사이트 정책", + "{{realm}} preview": "{{realm}} 미리보기", "1:1 Inquiry": "1:1 문의", "Account activation": "계정 활성화", "Accuracy Rate": "정답률", @@ -234,6 +235,7 @@ "Get Started {{sessionKind}}": "{{sessionKind}} 시작하기", "Getting started": "시작하기", "Getting Started": "시작하기", + "Go Back": "돌아가기", "goal": "목표", "Google Authenticator": "Google 인증", "Grade Confirm Due": "성적 확인 마감", @@ -358,7 +360,7 @@ "No related courses.": "연관된 과정이 없습니다.", "No similar answer found": "유사한 답안이 없습니다.", "No unread notification": "읽지 않은 알림이 없습니다", - "No watched media yet.": "시청한 미디어가 없습니다.", + "No watched public media yet.": "아직 시청된 공개 미디어가 없습니다.", "Not provided": "제공되지 않음", "Not reviewed / All appeals": "검토 안 됨 / 모든 이의신청", "Notification": "알림", @@ -377,6 +379,7 @@ "Otp Setup Completed Successfully": "OTP 설정 완료", "OTP Verification Required": "OTP 인증 필요", "Outline": "개요", + "Page Not Found": "페이지를 찾을 수 없습니다", "Paragraph": "단락", "Pass": "합격", "Passed": "합격", @@ -398,6 +401,7 @@ "Plagiarism threshold": "표절 기준", "Plagiarism threshold is {{threshold}}%. Over this threshold, the submission will be rejected.": "표절 검사 기준은 {{threshold}}%입니다. 이 수치보다 높으면 제출할 수 없습니다.", "Please agree to the following policies": "다음 정책에 동의해주세요", + "Please check the address and try again.": "주소를 확인하고 다시 시도해주세요.", "Please fill out the form below to join.": "가입하시려면 아래 양식을 작성해주세요.", "Please review carefully before submitting, as grade appeals become final once submitted.": "제출 전 내용을 확인하세요. 제출 후 성적 이의신청은 최종 확정됩니다.", "Please select a rating.": "평점을 선택하세요.", @@ -409,7 +413,6 @@ "Post point": "게시글 점수", "Post Point: {{count}}_other": "게시글 점수: {{count}}", "Preview": "미리보기", - "Preview mode": "미리보기 모드", "Preview URL": "미리보기 URL", "Privacy Policy": "개인정보 정책", "Private": "비공개", @@ -452,6 +455,7 @@ "Request email change": "이메일 변경 요청", "Request password change": "비밀번호 변경 요청", "required": "필수", + "Required": "필수", "required *": "필수 *", "Required *": "필수 *", "Researcher": "조사자", @@ -498,6 +502,7 @@ "Skill factors: {{num}}": "스킬 요소: {{num}}개", "Skills": "스킬", "Solved": "해결됨", + "Something went wrong": "문제가 발생했습니다", "SSO Error": "SSO 오류", "Staffs": "스태프", "Standard Score": "기준 점수", @@ -535,6 +540,7 @@ "The deadline for participating in the discussion is {{date}}.": "토론 참여 마감일: {{date}}", "The deadline for submitting the assignment is {{date}}.": "과제 제출 마감일: {{date}}", "The final grade confirmation due is {{date}}.": "최종 성적 확인 마감일: {{date}}", + "The page you are looking for does not exist or you do not have permission to access it.": "찾고 있는 페이지가 없거나 접근할 수 있는 권한이 없습니다.", "Third Level": "3단계", "This action cannot be undone. Are you sure you want to proceed?": "이 작업은 취소할 수 없습니다. 진행하시겠습니까?", "This course has no certificates.": "이 과정은 수료증이 없습니다.", @@ -560,6 +566,7 @@ "Type to search": "검색어 입력", "Unable to display timer": "타이머를 표시할 수 없습니다", "Unenroll": "등록 취소", + "Unknown Address": "알 수 없는 주소", "Unlimited": "무제한", "Unpublished": "미게시", "Update": "변경하기", diff --git a/web/src/routes/studio/-media/SubtitleSet.tsx b/web/src/routes/studio/-media/SubtitleSet.tsx index 5164fee..6b7032c 100644 --- a/web/src/routes/studio/-media/SubtitleSet.tsx +++ b/web/src/routes/studio/-media/SubtitleSet.tsx @@ -1,7 +1,15 @@ import { IconPlus } from '@tabler/icons-solidjs' import { batch, createSignal, For, Show } from 'solid-js' import { unwrap } from 'solid-js/store' -import { type MediaSpec, studioV1CreateMediaQuiz, studioV1DeleteMediaSubtitle, studioV1SaveMediaSubtitle } from '@/api' +import { + type MediaSpec, + studioV1ContentSuggestions, + studioV1CreateMediaQuiz, + studioV1DeleteMediaSubtitle, + studioV1SaveMediaSubtitle, +} from '@/api' +import { LANGUAGES } from '@/config' +import { createCachedStore } from '@/shared/solid/cached-store' import { useTranslation } from '@/shared/solid/i18n' import { type State, useEditing } from '../-context/editing' import { DataAction } from '../-studio/DataAction' @@ -83,16 +91,27 @@ const Subtitle = (props: { index: number }) => { const subtitleDirty = () => state()?.dirty ?? false const [inCreating, setInCreating] = createSignal(false) + + // quiz suggestions cache + const [quizSuggestions, { setStore: setQuizSuggestions }] = createCachedStore( + 'studioV1ContentSuggestions', + () => ({ query: { kind: 'quiz' as const } }), + async (options) => (await studioV1ContentSuggestions(options)).data, + ) + const createQuiz = async () => { setInCreating(true) const lang = staging.subtitles[props.index]!.lang try { - const { data, error } = await studioV1CreateMediaQuiz({ path: { id: staging.id, lang }, throwOnError: false }) - if (!error) { + const { data: id, error } = await studioV1CreateMediaQuiz({ path: { id: staging.id, lang }, throwOnError: false }) + if (!error && id) { + // add entry to quiz suggestions + const label = `${staging.title} - ${LANGUAGES.find((l) => l.value === lang)?.label ?? lang}` + setQuizSuggestions('data', quizSuggestions.data!.length, { id, label }) // error will be handled in global error handler batch(() => { - source.quizzes.push(data!) - staging.quizzes.push(data!) + source.quizzes.push(id) + staging.quizzes.push(id) }) } } finally {