Unified News: Unify lists and antennas into common tab

This commit is contained in:
Crimekillz 2024-05-02 21:04:21 +02:00
parent 16e0cc9471
commit e270504503
13 changed files with 360 additions and 310 deletions

View File

@ -136,7 +136,7 @@ unblockConfirm: "Sind Sie sicher, dass Sie die Sperrung dieses Kontos aufheben w
suspendConfirm: "Sind Sie sicher, dass Sie dieses Konto sperren wollen?" suspendConfirm: "Sind Sie sicher, dass Sie dieses Konto sperren wollen?"
unsuspendConfirm: "Sind Sie sicher, dass Sie dieses Konto entsperren wollen?" unsuspendConfirm: "Sind Sie sicher, dass Sie dieses Konto entsperren wollen?"
selectList: "Wählen Sie eine Liste aus" selectList: "Wählen Sie eine Liste aus"
selectAntenna: "News-Picker auswählen" selectAntenna: "Antenne auswählen"
selectWidget: "Ein Widget auswählen" selectWidget: "Ein Widget auswählen"
editWidgets: "Widgets bearbeiten" editWidgets: "Widgets bearbeiten"
editWidgetsExit: "Erledigt" editWidgetsExit: "Erledigt"
@ -389,10 +389,11 @@ recaptchaSecretKey: "Secret key"
avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen
führen. Sollen die anderen Systeme deaktiviert werden? Durch Abbrechen können mehrere führen. Sollen die anderen Systeme deaktiviert werden? Durch Abbrechen können mehrere
Systeme aktiviert bleiben." Systeme aktiviert bleiben."
antennas: "News-Picker" news: "Neuigkeiten"
manageAntennas: "News-Picker verwalten" antennas: "Antennen"
manageAntennas: "Antennen verwalten"
name: "Name" name: "Name"
antennaSource: "Quellen der News-Picker" antennaSource: "Quellen der Antennen"
antennaKeywords: "Zu beobachtende Schlüsselwörter" antennaKeywords: "Zu beobachtende Schlüsselwörter"
antennaExcludeKeywords: "Zu ignorierende Schlüsselwörter" antennaExcludeKeywords: "Zu ignorierende Schlüsselwörter"
antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen
@ -1328,7 +1329,7 @@ _sfx:
notification: "Benachrichtigungen" notification: "Benachrichtigungen"
chat: "Chat" chat: "Chat"
chatBg: "Chat (Hintergrund)" chatBg: "Chat (Hintergrund)"
antenna: "News-Picker" antenna: "Antennen"
channel: "Kanalbenachrichtigung" channel: "Kanalbenachrichtigung"
_ago: _ago:
future: "Zukunft" future: "Zukunft"

View File

@ -413,6 +413,7 @@ recaptchaSecretKey: "Secret key"
avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interference between avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interference between
them. Would you like to disable the other Captcha systems currently active? If you them. Would you like to disable the other Captcha systems currently active? If you
would like them to stay enabled, press cancel." would like them to stay enabled, press cancel."
news: "News-Picker"
antennas: "Antennas" antennas: "Antennas"
antennasDesc: "Antennas display new posts matching the criteria you set!\n They can antennasDesc: "Antennas display new posts matching the criteria you set!\n They can
be accessed from the timelines page." be accessed from the timelines page."

View File

@ -369,6 +369,7 @@ enableRecaptcha: "reCAPTCHAを有効にする"
recaptchaSiteKey: "サイトキー" recaptchaSiteKey: "サイトキー"
recaptchaSecretKey: "シークレットキー" recaptchaSecretKey: "シークレットキー"
avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますかキャンセルして複数のCaptchaを有効化したままにすることも可能です。" avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますかキャンセルして複数のCaptchaを有効化したままにすることも可能です。"
news: "ニュース"
antennas: "アンテナ" antennas: "アンテナ"
manageAntennas: "アンテナの管理" manageAntennas: "アンテナの管理"
name: "名前" name: "名前"

View File

@ -43,25 +43,11 @@ export const navbarItemDef = reactive({
icon: "ph-lightning ph-bold ph-lg", icon: "ph-lightning ph-bold ph-lg",
to: "/discover", to: "/discover",
}, },
lists: { news: {
title: "lists", title: "news",
icon: "ph-list-bullets ph-bold ph-lg",
show: computed(() => $i != null),
to: "/my/lists",
},
/*
groups: {
title: 'groups',
icon: 'ph-users-three ph-bold ph-lg',
show: computed(() => $i != null),
to: '/my/groups',
},
*/
antennas: {
title: "antennas",
icon: "ph-flying-saucer ph-bold ph-lg", icon: "ph-flying-saucer ph-bold ph-lg",
show: computed(() => $i != null), show: computed(() => $i != null),
to: "/my/antennas", to: "/news",
}, },
snippets: { snippets: {
title: "snippets", title: "snippets",

View File

@ -30,12 +30,12 @@ let draft = $ref({
}); });
function onAntennaCreated() { function onAntennaCreated() {
router.push("/my/antennas"); router.push("/news");
} }
definePageMetadata({ definePageMetadata({
title: i18n.ts.manageAntennas, title: i18n.ts.manageAntennas,
icon: "ph-flying-saucer ph-bold ph-lg", icon: "ph-cell-tower ph-bold ph-lg",
}); });
</script> </script>

View File

@ -25,7 +25,7 @@ const props = defineProps<{
}>(); }>();
function onAntennaUpdated() { function onAntennaUpdated() {
router.push("/my/antennas"); router.push("/news");
} }
os.api("antennas/show", { antennaId: props.antennaId }).then( os.api("antennas/show", { antennaId: props.antennaId }).then(
@ -40,7 +40,7 @@ const headerTabs = $computed(() => []);
definePageMetadata({ definePageMetadata({
title: i18n.ts.manageAntennas, title: i18n.ts.manageAntennas,
icon: "ph-flying-saucer ph-bold ph-lg", icon: "ph-cell-tower ph-bold ph-lg",
}); });
</script> </script>

View File

@ -1,153 +0,0 @@
<template>
<MkStickyContainer>
<template #header
><MkPageHeader
:actions="headerActions"
:tabs="headerTabs"
:display-back-button="true"
/></template>
<MkSpacer :content-max="700">
<div class="ieepwinx">
<MkInfo class="_gap" :icon="'flying-saucer'" :card="true">
<p>{{ i18n.ts.antennasDesc }}</p>
<MkButton
:link="true"
to="/my/antennas/create"
primary
class="add"
><i class="ph-plus ph-bold ph-lg"></i>
{{ i18n.ts.add }}</MkButton
>
</MkInfo>
<div class="">
<MkPagination ref="list" :pagination="pagination">
<template #default="{ items }">
<div v-for="antenna in items" :key="antenna.id">
<MkA
class="uopelskx"
:link="true"
:to="`/timeline/antenna/${antenna.id}`"
>
<i
class="ph-flying-saucer ph-bold ph-lg"
></i
><i
:class="`${
antenna.hasUnreadNote
? 'ph-circle ph-fill'
: 'ph-check'
} ph-xs notify-icon`"
></i>
</MkA>
<MkA
class="ljoevbzj"
:to="`/my/antennas/${antenna.id}`"
>
<div class="name">{{ antenna.name }}</div>
</MkA>
</div>
</template>
</MkPagination>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { onActivated, onDeactivated, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import MkInfo from "@/components/MkInfo.vue";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
const pagination = {
endpoint: "antennas/list" as const,
limit: 250,
};
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
const list = ref<typeof MkPagination | null>(null);
let isCached: boolean = false;
let refreshTimer: number | null = null;
const refresh = () => {
if (isCached) {
list.value?.refresh();
}
isCached = true;
refreshTimer = setTimeout(refresh, 15000);
};
onActivated(() => {
refresh();
});
onDeactivated(() => {
if (refreshTimer) {
clearTimeout(refreshTimer);
refreshTimer = null;
}
});
definePageMetadata({
title: i18n.ts.manageAntennas,
icon: "ph-flying-saucer ph-bold ph-lg",
});
</script>
<style lang="scss" scoped>
.ieepwinx {
.uopelskx {
float: left;
min-width: 25px;
padding: 13px;
margin-right: 10px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
}
.ljoevbzj {
display: block;
padding: 16px;
margin-bottom: 8px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
> .name {
font-weight: bold;
}
}
.notify-icon {
position: relative;
top: -1em;
left: -0.5em;
&.ph-circle ph-fill {
color: var(--indicator);
animation: blink 1s infinite;
}
&.ph-check {
color: var(--muted);
}
}
}
</style>

View File

@ -1,115 +0,0 @@
<template>
<MkStickyContainer>
<template #header
><MkPageHeader
:actions="headerActions"
:tabs="headerTabs"
:display-back-button="true"
/></template>
<MkSpacer :content-max="700">
<div class="qkcjvfiv">
<MkInfo class="_gap" :icon="'list-bullets'" :card="true">
<p>{{ i18n.ts.listsDesc }}</p>
<MkButton primary class="add" @click="create"
><i class="ph-plus ph-bold ph-lg"></i>
{{ i18n.ts.createList }}</MkButton
>
</MkInfo>
<MkPagination
v-slot="{ items }"
ref="pagingComponent"
:pagination="pagination"
class="lists _content"
>
<MkA
v-for="list in items"
:key="list.id"
class="list _panel"
:to="`/my/lists/${list.id}`"
>
<div class="name">{{ list.name }}</div>
<MkAvatars :user-ids="list.userIds" />
</MkA>
<MkButton @click="deleteAll"
><i class="ph-trash ph-bold ph-lg"></i>
{{ i18n.ts.deleteAll }}</MkButton
>
</MkPagination>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import {} from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import MkAvatars from "@/components/MkAvatars.vue";
import MkInfo from "@/components/MkInfo.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
const pagination = {
endpoint: "users/lists/list" as const,
limit: 10,
};
async function create() {
const { canceled, result: name } = await os.inputText({
title: i18n.ts.enterListName,
});
if (canceled) return;
await os.apiWithDialog("users/lists/create", { name: name });
pagingComponent.reload();
}
async function deleteAll() {
const { canceled } = await os.confirm({
type: "warning",
text: i18n.t("removeAreYouSure", { x: "all lists" }),
});
if (canceled) return;
await os.api("users/lists/delete-all");
os.success();
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.manageLists,
icon: "ph-list-bullets ph-bold ph-lg",
action: {
icon: "ph-plus ph-bold ph-lg",
handler: create,
},
});
</script>
<style lang="scss" scoped>
.qkcjvfiv {
> .lists {
> .list {
display: block;
padding: 16px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
> .name {
margin-bottom: 4px;
}
}
}
}
</style>

View File

@ -0,0 +1,332 @@
<template>
<MkStickyContainer>
<template #header
><MkPageHeader
v-model:tab="tab"
:actions="headerActions"
:tabs="headerTabs"
/></template>
<MkSpacer :content-max="800">
<swiper
:round-lengths="true"
:touch-angle="25"
:threshold="10"
:centeredSlides="true"
:modules="[Virtual]"
:space-between="20"
:virtual="true"
:allow-touch-move="
defaultStore.state.swipeOnMobile &&
(deviceKind !== 'desktop' ||
defaultStore.state.swipeOnDesktop)
"
@swiper="setSwiperRef"
@slide-change="onSlideChange"
>
<swiper-slide>
<div class="_content ieepwinx antennas">
<MkInfo class="_gap" :icon="'cell-tower'" :card="true">
<p>{{ i18n.ts.antennasDesc }}</p>
<MkButton
:link="true"
to="/my/antennas/create"
primary
class="add"
><i class="ph-plus ph-bold ph-lg"></i>
{{ i18n.ts.add }}</MkButton
>
</MkInfo>
<div class="">
<MkPagination ref="pagingComponent" :pagination="paginationAntenna">
<template #default="{ items }">
<div v-for="antenna in items" :key="antenna.id">
<MkA
class="uopelskx"
:link="true"
:to="`/my/antennas/${antenna.id}`"
>
<i
class="ph-gear ph-bold ph-lg"
></i
><i
:class="`${
antenna.hasUnreadNote
? 'ph-circle ph-fill'
: 'ph-check'
} ph-xs notify-icon`"
></i>
</MkA>
<MkA
class="ljoevbzj"
:to="`/timeline/antenna/${antenna.id}`"
>
<div class="name">{{ antenna.name }}</div>
</MkA>
</div>
</template>
</MkPagination>
</div>
</div>
</swiper-slide>
<swiper-slide>
<div class="_content qkcjvfiv lists">
<MkInfo class="_gap" :icon="'list-bullets'" :card="true">
<p>{{ i18n.ts.listsDesc }}</p>
<MkButton primary class="add" @click="createList"
><i class="ph-plus ph-bold ph-lg"></i>
{{ i18n.ts.createList }}</MkButton
>
</MkInfo>
<MkPagination
v-slot="{ items }"
ref="pagingComponent"
:pagination="paginationList"
class="lists _content"
>
<MkA
v-for="list in items"
:key="list.id"
class="list _panel"
:to="`/timeline/list/${list.id}`"
>
<MkA
class="uopelskx"
:link="true"
:to="`/my/lists/${list.id}`"
>
<i class="ph-gear ph-bold ph-lg"></i>
</MkA>
<div class="name">{{ list.name }}</div>
<MkAvatars :user-ids="list.userIds" />
</MkA>
<MkButton @click="deleteAllLists"
><i class="ph-trash ph-bold ph-lg"></i>
{{ i18n.ts.deleteAll }}</MkButton
>
</MkPagination>
</div>
</swiper-slide>
</swiper>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { onActivated, onDeactivated, onMounted, ref, watch } from "vue";
import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import MkPagination from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import MkAvatars from "@/components/MkAvatars.vue";
import * as os from "@/os";
import MkInfo from "@/components/MkInfo.vue";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import { deviceKind } from "@/scripts/device-kind";
import { defaultStore } from "@/store";
import "swiper/scss";
import "swiper/scss/virtual";
const paginationAntenna = {
endpoint: "antennas/list" as const,
limit: 250,
};
const paginationList = {
endpoint: "users/lists/list" as const,
limit: 10,
};
const tabs = ["antennas", "lists"];
let tab = $ref(tabs[0]);
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
const MOBILE_THRESHOLD = 500;
const isMobile = ref(
deviceKind === "smartphone" || window.innerWidth <= MOBILE_THRESHOLD,
);
window.addEventListener("resize", () => {
isMobile.value =
deviceKind === "smartphone" || window.innerWidth <= MOBILE_THRESHOLD;
});
const headerActions = $computed(() => []);
const headerTabs = $computed(() => [
{
key: "antennas",
title: i18n.ts.antennas,
icon: "ph-cell-tower ph-bold ph-lg",
},
{
key: "lists",
title: i18n.ts.lists,
icon: "ph-list-bullets ph-bold ph-lg",
},
]);
const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
let isCached: boolean = false;
let refreshTimer: number | null = null;
const refresh = () => {
if (isCached) {
pagingComponent.value?.refresh();
}
isCached = true;
refreshTimer = setTimeout(refresh, 15000);
};
async function createList() {
const { canceled, result: name } = await os.inputText({
title: i18n.ts.enterListName,
});
if (canceled) return;
await os.apiWithDialog("users/lists/create", { name: name });
pagingComponent.reload();
}
async function deleteAllLists() {
const { canceled } = await os.confirm({
type: "warning",
text: i18n.t("removeAreYouSure", { x: "all lists" }),
});
if (canceled) return;
await os.api("users/lists/delete-all");
os.success();
}
let swiperRef = null;
function setSwiperRef(swiper) {
swiperRef = swiper;
syncSlide(tabs.indexOf(tab));
}
function onSlideChange() {
tab = tabs[swiperRef.activeIndex];
}
function syncSlide(index) {
swiperRef.slideTo(index);
}
onMounted(() => {
syncSlide(tabs.indexOf(swiperRef.activeIndex));
});
onActivated(() => {
refresh();
});
onDeactivated(() => {
if (refreshTimer) {
clearTimeout(refreshTimer);
refreshTimer = null;
}
});
definePageMetadata({
title: i18n.ts.news,
icon: "ph-flying-saucer ph-bold ph-lg",
});
</script>
<style lang="scss" scoped>
.ieepwinx {
.uopelskx {
float: left;
min-width: 20px;
padding: 11px;
margin-right: 10px;
border: solid 1px var(--divider);
border-radius: 6px;
margin-left: 5px;
margin-top: 5px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
}
.ljoevbzj {
display: block;
padding: 16px;
margin-bottom: 8px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
> .name {
font-weight: bold;
}
}
.notify-icon {
position: relative;
top: -1em;
left: -0.5em;
&.ph-circle ph-fill {
color: var(--indicator);
animation: blink 1s infinite;
}
&.ph-check {
color: var(--muted);
}
}
}
.qkcjvfiv {
.uopelskx {
float: left;
min-width: 20px;
padding: 11px;
margin-right: 10px;
border: solid 1px var(--divider);
border-radius: 6px;
margin-left: -5px;
margin-top: -5px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
}
.defgtij {
padding: 0px;
place-content: baseline;
}
> .lists {
> .list {
display: block;
padding: 16px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
> .name {
margin-top: 6px;
margin-bottom: 28px;
}
}
}
}
</style>

View File

@ -209,17 +209,19 @@ const headerActions = $computed(() => [
handler: chooseList, handler: chooseList,
}, },
{ {
icon: "ph-flying-saucer ph-bold ph-lg", icon: "ph-cell-tower ph-bold ph-lg",
title: i18n.ts.antennas, title: i18n.ts.antennas,
text: i18n.ts.antennas, text: i18n.ts.antennas,
iconOnly: true, iconOnly: true,
handler: chooseAntenna, handler: chooseAntenna,
} /* **TODO: fix timetravel** { }/* **TODO: fix timetravel**,
icon: 'ph-calendar-blank ph-bold ph-lg', {
icon: 'ph-clock-counter-clockwise ph-bold ph-lg',
title: i18n.ts.jumpToSpecifiedDate, title: i18n.ts.jumpToSpecifiedDate,
text: i18n.ts.jumpToSpecifiedDate,
iconOnly: true, iconOnly: true,
handler: timetravel, handler: timetravel,
}*/, }*/
]); ]);
const headerTabs = $computed(() => [ const headerTabs = $computed(() => [

View File

@ -635,11 +635,6 @@ export const routes = [
component: page(() => import("./pages/my-lists/list.vue")), component: page(() => import("./pages/my-lists/list.vue")),
loginRequired: true, loginRequired: true,
}, },
{
path: "/my/lists",
component: page(() => import("./pages/my-lists/index.vue")),
loginRequired: true,
},
{ {
path: "/my/groups", path: "/my/groups",
component: page(() => import("./pages/my-groups/index.vue")), component: page(() => import("./pages/my-groups/index.vue")),
@ -661,8 +656,8 @@ export const routes = [
loginRequired: true, loginRequired: true,
}, },
{ {
path: "/my/antennas", path: "/news",
component: page(() => import("./pages/my-antennas/index.vue")), component: page(() => import("./pages/news.vue")),
loginRequired: true, loginRequired: true,
}, },
{ {

View File

@ -14,7 +14,7 @@ const menuOptions = [
"messaging", "messaging",
"-", "-",
"snippets", "snippets",
"antennas", "news",
"discover", "discover",
"-", "-",
"achievements", "achievements",

View File

@ -6,7 +6,7 @@
@parent-focus="($event) => emit('parent-focus', $event)" @parent-focus="($event) => emit('parent-focus', $event)"
> >
<template #header> <template #header>
<i class="ph-flying-saucer ph-bold ph-lg"></i <i class="ph-cell-tower ph-bold ph-lg"></i
><span style="margin-left: 8px">{{ column.name }}</span> ><span style="margin-left: 8px">{{ column.name }}</span>
</template> </template>