mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 07:41:02 +08:00
215 lines
5.7 KiB
Vue
215 lines
5.7 KiB
Vue
<script setup lang="ts">
|
|
import { GlobeAltIcon } from "@heroicons/vue/24/outline";
|
|
import { Facebook } from "lucide-vue-next";
|
|
import { gsap } from "gsap";
|
|
import { ScrambleTextPlugin } from "gsap/dist/ScrambleTextPlugin";
|
|
gsap.registerPlugin(ScrambleTextPlugin);
|
|
const loading = ref(true);
|
|
const { t, locale } = useI18n();
|
|
import translate from "translate";
|
|
|
|
interface translateInterfaceText {
|
|
translateText: string;
|
|
}
|
|
const translateItem: Record<string, translateInterfaceText> = {};
|
|
|
|
const emit = defineEmits([
|
|
"windowopener",
|
|
"error",
|
|
"loadValue",
|
|
"openArticles",
|
|
]);
|
|
|
|
const props = defineProps({
|
|
values: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
applyForTranslation: Boolean,
|
|
});
|
|
|
|
const staticProps = props;
|
|
|
|
const {
|
|
data: fetchNewsOrgInfo,
|
|
pending,
|
|
error,
|
|
} = useFetch(`/api/publishers/lt/${staticProps.values}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: {
|
|
lang: locale,
|
|
requestPage: props.values,
|
|
},
|
|
});
|
|
|
|
const orgNameAnimation = ref(null);
|
|
|
|
watch(
|
|
() => fetchNewsOrgInfo.value,
|
|
(newValue) => {
|
|
if (newValue?.title) {
|
|
nextTick(() => {
|
|
gsap.to(orgNameAnimation.value, {
|
|
duration: 1,
|
|
scrambleText: newValue.title,
|
|
ease: "none",
|
|
});
|
|
});
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
const openNews = (url: string, titleName: string) => {
|
|
emit("openArticles", url, titleName);
|
|
};
|
|
|
|
const startTranslating = async (text: string) => {
|
|
try {
|
|
console.log(text);
|
|
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 to original text
|
|
}
|
|
};
|
|
|
|
// Translating logic
|
|
const translateText = ref(false);
|
|
const translatedBefore = ref(false);
|
|
const traslateFailed = ref(false);
|
|
const displayTranslatedText = ref(false);
|
|
const loadingTranslations = ref(false);
|
|
watch(
|
|
() => props.applyForTranslation,
|
|
(value) => {
|
|
if (value === true) {
|
|
translateText.value = true;
|
|
if (!fetchNewsOrgInfo.value) {
|
|
return;
|
|
}
|
|
if (translatedBefore.value === true) {
|
|
displayTranslatedText.value = true;
|
|
return;
|
|
}
|
|
loadingTranslations.value = true;
|
|
startTranslating(fetchNewsOrgInfo.value?.title);
|
|
startTranslating(fetchNewsOrgInfo.value?.description);
|
|
for (const articles of fetchNewsOrgInfo.value?.articles) {
|
|
startTranslating(articles.title.replaceAll("獨家專欄》", ""));
|
|
startTranslating(articles.date);
|
|
}
|
|
// NOT retranslating AGAIN when disabling the feat
|
|
translatedBefore.value = true;
|
|
setTimeout(() => {
|
|
displayTranslatedText.value = true;
|
|
loadingTranslations.value = false;
|
|
}, 3000);
|
|
} else {
|
|
translateText.value = false;
|
|
displayTranslatedText.value = false;
|
|
}
|
|
},
|
|
); // Translate when requested?
|
|
</script>
|
|
<template>
|
|
<div v-if="loadingTranslations">Loading...</div>
|
|
|
|
<div>
|
|
<div class="text-center align-center justify-center">
|
|
<div
|
|
class="flex flex-row bg-gray-300/70 rounded-3xl p-3 gap-3 m-3 scale-5"
|
|
>
|
|
<div class="flex flex-col gap-3 text-left w-full">
|
|
<h1
|
|
v-if="pending"
|
|
class="h-12 bg-gray-200 animate-pulse rounded m-2 w-3/4 mx-auto"
|
|
></h1>
|
|
<h1
|
|
v-else
|
|
class="text-4xl font-bold m-2 text-center"
|
|
ref="orgNameAnimation"
|
|
>
|
|
{{
|
|
displayTranslatedText
|
|
? translateItem[fetchNewsOrgInfo?.title].translateText
|
|
: fetchNewsOrgInfo?.title
|
|
}}
|
|
</h1>
|
|
|
|
<div v-if="pending" class="flex flex-col gap-2 m-1 mt-5">
|
|
<div class="h-4 bg-gray-200 animate-pulse rounded w-full"></div>
|
|
<div class="h-4 bg-gray-200 animate-pulse rounded w-5/6"></div>
|
|
<div class="h-4 bg-gray-200 animate-pulse rounded w-4/6"></div>
|
|
</div>
|
|
<span v-else class="text-ms m-1 mt-5 text-left text-wrap">
|
|
{{
|
|
displayTranslatedText
|
|
? translateItem[fetchNewsOrgInfo?.description].translateText
|
|
: fetchNewsOrgInfo?.description
|
|
}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3">
|
|
<template v-if="pending">
|
|
<div
|
|
v-for="item in 5"
|
|
:key="item"
|
|
class="p-3 bg-gray-300/70 rounded m-1 animate-pulse h-8"
|
|
></div>
|
|
</template>
|
|
<template v-else>
|
|
<hr />
|
|
<h3 class="text-2xl text-bold">文章</h3>
|
|
<button
|
|
v-for="item in fetchNewsOrgInfo?.articles"
|
|
@click="() => openNews(item.link, item.title)"
|
|
class="p-1 bg-gray-300/70 rounded min-h-4 w-full"
|
|
>
|
|
<div>
|
|
<div class="flex flex-col">
|
|
<span class="title text-bold texxt-sm">{{
|
|
displayTranslatedText
|
|
? translateItem[item.title.replaceAll("獨家專欄》", "")]
|
|
.translateText
|
|
: item.title.replaceAll("獨家專欄》", "")
|
|
}}</span>
|
|
<span class="date text-xs">{{
|
|
displayTranslatedText
|
|
? translateItem[item.date].translateText
|
|
: item.date
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.animate-pulse {
|
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
</style>
|