Compare commits

...

4 commits

Author SHA1 Message Date
04ad8ca58e Add API endpoint for the current server version.
Some checks are pending
Build and Push Docker Image / build-and-push (push) Waiting to run
2025-06-08 21:38:02 +08:00
dd39bc3910 Add version tag system, for matching if the deployment is the newest one
or not.
2025-06-08 21:36:37 +08:00
61de16e285 Testing the working transtion feat in prod :) 2025-06-08 21:02:37 +08:00
b84a0a6f18 Add translations in to the news window (and also broke it... 2025-06-08 20:43:16 +08:00
8 changed files with 133 additions and 32 deletions

View file

@ -14,7 +14,7 @@
# Copy source files
COPY . .
RUN bun run generateVersionTag
# Build the application
RUN bun run build

View file

@ -1,9 +1,14 @@
<script setup lang="ts">
// Translate stuff
// Vars for translating stuff
interface translateInterfaceText {
translateText: string;
}
const translateItems: Record<string, translateInterfaceText> = {};
const translateItem: Record<string, translateInterfaceText> = {};
const translateLoading = ref(false);
const displayTranslateContent = ref(false);
const traslateFailed = ref(false);
const translatedBefore = ref(false);
// Imports
import { ScanEyeIcon, RefreshCcwIcon } from "lucide-vue-next";
@ -32,18 +37,10 @@ const emit = defineEmits([
"windowopener",
]);
const props = defineProps({
applyForTranslation: {
type: Boolean,
required: true,
},
windowTranslateState: {
type: Boolean,
required: true,
},
});
const { applyForTranslation, windowTranslateState } = props;
const props = defineProps<{
applyForTranslation: Boolean;
windowTranslateState: Boolean;
}>();
const openNewWindow = (itemId: string) => {
emit("windowopener", "aboutNewsOrg");
@ -53,7 +50,7 @@ const contentArray = ref([]);
const errorr = ref(false);
const switchTabs = ref(false);
const tabs = ref([]);
const primary = ref<string>("top"); // Hard code value fn
const primary = ref<string>("domestic"); // Hard code default value as top is just pure garbage.
const canNotLoadTabUI = ref(false);
const isDataCached = ref(false);
const pullTabsData = async () => {
@ -84,6 +81,7 @@ const updateContent = async (url: string, tabAction: boolean) => {
contentArray.value = [...data.uuidData, ...(data.nuuiddata?.items || [])];
switchTabs.value = false;
isDataCached.value = data.cached || false;
translatedBefore.value = false;
}
} catch (e) {
console.log(e);
@ -215,15 +213,64 @@ const openPublisher = (slug: string, title: string) => {
emit("openNewsSourcePage", slug, title);
};
const isLoading = computed(() => contentArray.value.length === 0);
const testmessage = await translate("嗨", { from: "zh", to: "en" });
const shouldHideItem = (item) => {
return (
item.contentType !== "GENERAL" ||
item.publisher?.toLowerCase().includes("line")
);
};
// Translate (Selective content)
const startTranslating = async (text: string) => {
try {
translateItem[text] = {
translateText: await translate(text, { from: "zh", to: "en" }),
};
console.log(translateItem[text]);
} catch (error) {
console.error("Translation failed:", error);
traslateFailed.value = true;
translateItem[text] = { translateText: text }; // fallback
}
};
watch(
() => props.applyForTranslation,
(value) => {
if (value === true || translatedBefore.value === false) {
if (translatedBefore.value === true) {
displayTranslateContent.value = true;
return;
}
translateFunction();
// NOT retranslating AGAIN when disabling the feat
translatedBefore.value = true;
} else {
displayTranslateContent.value = false;
}
},
);
const translateFunction = () => {
if (canNotLoadTabUI.value) {
return;
}
translateLoading.value = true;
// Translate tabs
for (const tab of tabs.value) {
startTranslating(tab.text);
}
// Translate news titles & news org
for (const articleBlock of contentArray.value) {
startTranslating(articleBlock.title);
startTranslating(articleBlock.publisher);
}
setTimeout(() => {
displayTranslateContent.value = true;
translateLoading.value = false;
}, 3000);
};
</script>
<template>
<div v-if="translateLoading">Loading...</div>
<div class="justify-center align-center text-center">
<!--Tabs-->
<div
@ -252,13 +299,16 @@ const shouldHideItem = (item) => {
class="disabled:cursor-not-allowed"
:disabled="isPrimary(item.url, true) || switchTabs"
>
<span>{{ true ? item.text : testmessage }}</span>
<span>{{
displayTranslateContent
? translateItem[item.text].translateText
: item.text
}}</span>
</button>
</template>
<button v-if="canNotLoadTabUI"><RefreshCcwIcon /></button>
</div>
</div>
<!-- Content Area -->
<div>
<!-- Loading State -->
@ -325,11 +375,19 @@ const shouldHideItem = (item) => {
<button
@click="openPublisher(item.publisherId, item.publisher)"
>
{{ item.publisher }}
{{
displayTranslateContent
? translateItem[item.publisher].translateText
: item.publisher
}}
</button>
</TooltipTrigger>
<TooltipContent class="rounded">
會打開關於媒體({{ item.publisher }})的視窗
{{ t("news.articleopenpart1") }}({{
displayTranslateContent
? translateItem[item.publisher].translateText
: item.publisher
}}){{ t("news.articleopenpart2") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
@ -355,18 +413,20 @@ const shouldHideItem = (item) => {
@click="openNews(item.url.hash, item.title)"
class="flex flex-row p-1 bg-sky-300/50 hover:bg-sky-400/50 shadow-lg backdrop-blur-sm rounded transition-all duration-200"
>
<ScanEyeIcon class="w-6 h-6 p-1" /><span>觀看文章</span>
<ScanEyeIcon class="w-6 h-6 p-1" /><span>{{
t("news.open")
}}</span>
</button>
</TooltipTrigger>
<TooltipContent class="rounded">
會打開新的視窗
{{ t("news.opennewwindow") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div>
<div>
<h3 class="text-lg">類似文章</h3>
<h3 class="text-lg">{{ t("news.similararticles") }}</h3>
<div class="space-y-2">
<div
v-for="similar in useArgFindRel(item.title, item.publisher)"
@ -376,8 +436,13 @@ const shouldHideItem = (item) => {
>
<div class="font-medium">{{ similar.title }}</div>
<div class="text-gray-500 text-xs">
相似度: {{ (similar.similarity * 100).toFixed(1) }}% |
{{ similar.item.publisher }}
{{ t("news.similarity") }}:
{{ (similar.similarity * 100).toFixed(1) }}% |
{{
displayTranslateContent
? translateItem[similar.item.publisher].translateText
: similar.item.publisher
}}
</div>
</div>
</div>
@ -385,7 +450,7 @@ const shouldHideItem = (item) => {
v-if="checkIfEmpty(item.title)"
class="text-gray-500 text-sm"
>
找不到類似文章
{{ t("news.nosimilararticles") }}
</div>
</div>
</div>

View file

@ -10,6 +10,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import getVersionTag from "~/versionTag";
import { Button } from "@/components/ui/button";
const { t, locale } = useI18n();
const user = ref("");
@ -281,20 +282,20 @@ const submitChangeAction = async (action: string) => {
>
<button
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
@click="emit('windowopener', 'privacypolicy')"
@click="() => emit('windowopener', 'privacypolicy')"
>
Privacy Policy
</button>
<button
class="bg-sky-400 p-1 rounded hover:bg-sky-600 transition-all duration-200 w-32"
@click="emit('windowopener', 'tos')"
@click="() => emit('windowopener', 'tos')"
>
TOS
</button>
</div>
<hr />
<div class="justiy-center align-center text-center">
{{ t("app.settings") }} v0.0.3
{{ t("app.settings") }} v0.0.3 || Version: {{ getVersionTag() }}
</div>
</div>
</template>

View file

@ -128,5 +128,14 @@
"contactEmailStarter": "聯絡信箱:"
},
"copyrightInfo": "版權資訊"
},
"news": {
"open": "觀看文章",
"opennewwindow": "會打開新的視窗",
"similararticles": "類似文章",
"similarity": "相似度",
"nosimilararticles": "找不到類似文章",
"articleopenpart1": "會打開關於媒體",
"articleopenpart2": "的視窗"
}
}

View file

@ -14,7 +14,8 @@
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs",
"wipedev": "./clean-dev-env.sh"
"wipedev": "./clean-dev-env.sh",
"generateVersionTag": "bun run versionTagGenerate.ts"
},
"dependencies": {
"@fontsource-variable/noto-sans-tc": "^5.2.5",

6
server/api/version.ts Normal file
View file

@ -0,0 +1,6 @@
import versionTag from "~/versionTag";
export default defineEventHandler(() => {
return {
version: versionTag(),
};
});

3
versionTag.ts Normal file
View file

@ -0,0 +1,3 @@
export default function versionTag() {
return "value";
}

16
versionTagGenerate.ts Normal file
View file

@ -0,0 +1,16 @@
import { $ } from "bun";
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
function generateVersionTag() {
let slug = "";
let length = 8;
for (let times = 0; times < length; times++) {
slug += characters.charAt(Math.floor(Math.random() * characters.length));
}
return slug;
}
const tag = generateVersionTag();
// Command
await $`echo 'export default function versionTag() {return "${tag}";}' > ./versionTag.ts`;
console.log("Version Tag:", tag);