mirror of
https://iceshrimp.dev/crimekillz/trashposs
synced 2024-11-24 01:39:06 +01:00
Unified Content Discovery
This commit is contained in:
parent
6f57bce3aa
commit
77a9a49ca1
@ -278,6 +278,8 @@ uploadFromUrlRequested: "Upload angefordert"
|
|||||||
uploadFromUrlMayTakeTime: "Es kann einige Zeit dauern, bis das Hochladen abgeschlossen
|
uploadFromUrlMayTakeTime: "Es kann einige Zeit dauern, bis das Hochladen abgeschlossen
|
||||||
ist."
|
ist."
|
||||||
explore: "Erkunden"
|
explore: "Erkunden"
|
||||||
|
discover: "Entdecken"
|
||||||
|
reel: "Reel"
|
||||||
messageRead: "Gelesen"
|
messageRead: "Gelesen"
|
||||||
noMoreHistory: "Es gibt keine weitere Historie"
|
noMoreHistory: "Es gibt keine weitere Historie"
|
||||||
startMessaging: "Einen neuen Chat beginnen"
|
startMessaging: "Einen neuen Chat beginnen"
|
||||||
|
@ -302,6 +302,8 @@ uploadFromUrlDescription: "URL of the file you want to upload"
|
|||||||
uploadFromUrlRequested: "Upload requested"
|
uploadFromUrlRequested: "Upload requested"
|
||||||
uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
||||||
explore: "Explore"
|
explore: "Explore"
|
||||||
|
discover: "Discover"
|
||||||
|
reel: "Reel"
|
||||||
messageRead: "Read"
|
messageRead: "Read"
|
||||||
noMoreHistory: "There is no further history"
|
noMoreHistory: "There is no further history"
|
||||||
startMessaging: "Start a new chat"
|
startMessaging: "Start a new chat"
|
||||||
|
@ -264,6 +264,8 @@ uploadFromUrlDescription: "アップロードしたいファイルのURL"
|
|||||||
uploadFromUrlRequested: "アップロードをリクエストしました"
|
uploadFromUrlRequested: "アップロードをリクエストしました"
|
||||||
uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
|
uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
|
||||||
explore: "みつける"
|
explore: "みつける"
|
||||||
|
discover: "Discover"
|
||||||
|
reel: "Reel"
|
||||||
messageRead: "既読"
|
messageRead: "既読"
|
||||||
noMoreHistory: "これより過去の履歴はありません"
|
noMoreHistory: "これより過去の履歴はありません"
|
||||||
startMessaging: "チャットを開始"
|
startMessaging: "チャットを開始"
|
||||||
|
@ -210,7 +210,7 @@ import { reactive, computed } from "vue";
|
|||||||
import XSettings from "@/pages/settings/profile.vue";
|
import XSettings from "@/pages/settings/profile.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import XFeaturedUsers from "@/pages/explore.users.vue";
|
import XFeaturedUsers from "@/pages/discover.users.vue";
|
||||||
import XPostForm from "@/components/MkPostForm.vue";
|
import XPostForm from "@/components/MkPostForm.vue";
|
||||||
import MkSparkle from "@/components/MkSparkle.vue";
|
import MkSparkle from "@/components/MkSparkle.vue";
|
||||||
import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue";
|
import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue";
|
||||||
|
@ -38,15 +38,10 @@ export const navbarItemDef = reactive({
|
|||||||
indicated: computed(() => $i?.hasPendingReceivedFollowRequest),
|
indicated: computed(() => $i?.hasPendingReceivedFollowRequest),
|
||||||
to: "/my/follow-requests",
|
to: "/my/follow-requests",
|
||||||
},
|
},
|
||||||
explore: {
|
discover: {
|
||||||
title: "explore",
|
title: "discover",
|
||||||
icon: "ph-hash ph-bold ph-lg",
|
icon: "ph-lightning ph-bold ph-lg",
|
||||||
to: "/explore",
|
to: "/discover",
|
||||||
},
|
|
||||||
search: {
|
|
||||||
title: "search",
|
|
||||||
icon: "ph-magnifying-glass ph-bold ph-lg",
|
|
||||||
to: "/search",
|
|
||||||
},
|
},
|
||||||
lists: {
|
lists: {
|
||||||
title: "lists",
|
title: "lists",
|
||||||
@ -81,7 +76,7 @@ export const navbarItemDef = reactive({
|
|||||||
},
|
},
|
||||||
gallery: {
|
gallery: {
|
||||||
title: "gallery",
|
title: "gallery",
|
||||||
icon: "ph-image-square ph-bold ph-lg",
|
icon: "ph-film-strip ph-bold ph-lg",
|
||||||
to: "/gallery",
|
to: "/gallery",
|
||||||
},
|
},
|
||||||
channels: {
|
channels: {
|
||||||
|
342
packages/client/src/pages/discover.vue
Normal file
342
packages/client/src/pages/discover.vue
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header
|
||||||
|
><MkPageHeader
|
||||||
|
v-model:tab="tab"
|
||||||
|
:actions="headerActions"
|
||||||
|
:tabs="headerTabs"
|
||||||
|
/></template>
|
||||||
|
<div class="lznhrdub">
|
||||||
|
<MkSpacer :content-max="1200">
|
||||||
|
<MkSearch :query="searchQuery" :hideFilters="!$i || tab !== 'featured'" @query="search"/>
|
||||||
|
<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>
|
||||||
|
<template v-if="searchQuery == null || searchQuery.trim().length < 1">
|
||||||
|
<XUsers />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="tabs[swiperRef!.activeIndex] == 'users'">
|
||||||
|
<XUserList
|
||||||
|
ref="users"
|
||||||
|
class="_gap"
|
||||||
|
:pagination="usersSearchPagination"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</swiper-slide>
|
||||||
|
<swiper-slide>
|
||||||
|
<template v-if="$i">
|
||||||
|
<template v-if="searchQuery == null || searchQuery.trim().length < 1">
|
||||||
|
<XFeatured />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="tabs[swiperRef!.activeIndex] == 'notes'">
|
||||||
|
<XNotes ref="notes" :pagination="notesSearchPagination" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<XFeatured />
|
||||||
|
</template>
|
||||||
|
</swiper-slide>
|
||||||
|
<swiper-slide>
|
||||||
|
<div class="_content grwlizim featured">
|
||||||
|
<MkChannelList
|
||||||
|
key="featured"
|
||||||
|
:pagination="chanTrendPagination"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</swiper-slide>
|
||||||
|
<swiper-slide>
|
||||||
|
<div class="rknalgpo">
|
||||||
|
<MkPagination
|
||||||
|
v-slot="{ items }"
|
||||||
|
:pagination="pagesTrendPagination"
|
||||||
|
>
|
||||||
|
<MkPagePreview
|
||||||
|
v-for="page in items"
|
||||||
|
:key="page.id"
|
||||||
|
class="ckltabjg"
|
||||||
|
:page="page"
|
||||||
|
/>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
</swiper-slide>
|
||||||
|
<swiper-slide>
|
||||||
|
<MkFolder class="_gap">
|
||||||
|
<template #header
|
||||||
|
><i class="ph-clock ph-bold ph-lg"></i>
|
||||||
|
{{ i18n.ts.recentPosts }}</template
|
||||||
|
>
|
||||||
|
<MkPagination
|
||||||
|
v-slot="{ items }"
|
||||||
|
:pagination="recentReelsPagination"
|
||||||
|
:disable-auto-load="true"
|
||||||
|
>
|
||||||
|
<div class="vfpdbgtk">
|
||||||
|
<MkGalleryPostPreview
|
||||||
|
v-for="post in items"
|
||||||
|
:key="post.id"
|
||||||
|
:post="post"
|
||||||
|
class="post"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
|
</MkFolder>
|
||||||
|
<MkFolder class="_gap">
|
||||||
|
<template #header
|
||||||
|
><i class="ph-fire-simple ph-bold ph-lg"></i>
|
||||||
|
{{ i18n.ts.popularPosts }}</template
|
||||||
|
>
|
||||||
|
<MkPagination
|
||||||
|
v-slot="{ items }"
|
||||||
|
:pagination="popularReelsPagination"
|
||||||
|
:disable-auto-load="true"
|
||||||
|
>
|
||||||
|
<div class="vfpdbgtk">
|
||||||
|
<MkGalleryPostPreview
|
||||||
|
v-for="post in items"
|
||||||
|
:key="post.id"
|
||||||
|
:post="post"
|
||||||
|
class="post"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
|
</MkFolder>
|
||||||
|
</swiper-slide>
|
||||||
|
</swiper>
|
||||||
|
</MkSpacer>
|
||||||
|
</div>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, watch, onMounted, onActivated } from "vue";
|
||||||
|
import { Virtual } from "swiper/modules";
|
||||||
|
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||||
|
import MkChannelPreview from "@/components/MkChannelPreview.vue";
|
||||||
|
import MkChannelList from "@/components/MkChannelList.vue";
|
||||||
|
import MkPagePreview from "@/components/MkPagePreview.vue";
|
||||||
|
import MkGalleryPostPreview from "@/components/MkGalleryPostPreview.vue";
|
||||||
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import XFeatured from "./discover.featured.vue";
|
||||||
|
import XUsers from "./discover.users.vue";
|
||||||
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { $i } from "@/account";
|
||||||
|
import MkSearch from "@/components/MkSearch.vue";
|
||||||
|
import XNotes from "@/components/MkNotes.vue";
|
||||||
|
import XUserList from "@/components/MkUserList.vue";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import "swiper/scss";
|
||||||
|
import "swiper/scss/virtual";
|
||||||
|
import { useRouter } from "@/router.js";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const tabs = ["users", "featured", "channels", "pages", "reel"];
|
||||||
|
let tab = $ref(tabs[0]);
|
||||||
|
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
|
||||||
|
|
||||||
|
const getUrlParams = () =>
|
||||||
|
window.location.search
|
||||||
|
.substring(1)
|
||||||
|
.split("&")
|
||||||
|
.reduce((result, query) => {
|
||||||
|
const [k, v] = query.split("=");
|
||||||
|
result[k] = decodeURIComponent(v?.replace('+', '%20'));
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
let searchQuery = $ref<string>(getUrlParams()['q'] ?? "");
|
||||||
|
let channel = $ref<string|undefined>(getUrlParams()['channel'] ?? undefined);
|
||||||
|
|
||||||
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
|
const headerTabs = $computed(() => [
|
||||||
|
{
|
||||||
|
key: "users",
|
||||||
|
icon: "ph-users ph-bold ph-lg",
|
||||||
|
title: i18n.ts.users,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "featured",
|
||||||
|
icon: "ph-fire ph-bold ph-lg",
|
||||||
|
title: i18n.ts.notes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "channels",
|
||||||
|
icon: "ph-megaphone-simple ph-bold ph-lg",
|
||||||
|
title: i18n.ts.channel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "pages",
|
||||||
|
icon: "ph-file-text ph-bold ph-lg",
|
||||||
|
title: i18n.ts.pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "reel",
|
||||||
|
icon: "ph-film-strip ph-bold ph-lg",
|
||||||
|
title: i18n.ts.reel,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const chanTrendPagination = {
|
||||||
|
endpoint: "channels/featured" as const,
|
||||||
|
limit: 10,
|
||||||
|
noPaging: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const pagesTrendPagination = {
|
||||||
|
endpoint: "pages/featured" as const,
|
||||||
|
limit: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const recentReelsPagination = {
|
||||||
|
endpoint: "gallery/posts" as const,
|
||||||
|
limit: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
const popularReelsPagination = {
|
||||||
|
endpoint: "gallery/featured" as const,
|
||||||
|
limit: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const notesSearchPagination = {
|
||||||
|
endpoint: "notes/search" as const,
|
||||||
|
limit: 10,
|
||||||
|
params: computed(() => ({
|
||||||
|
query: searchQuery,
|
||||||
|
channelId: channel,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const usersSearchPagination = {
|
||||||
|
endpoint: "users/search" as const,
|
||||||
|
limit: 10,
|
||||||
|
params: computed(() => ({
|
||||||
|
query: searchQuery,
|
||||||
|
origin: "combined",
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
definePageMetadata(
|
||||||
|
computed(() => ({
|
||||||
|
title: i18n.ts.discover,
|
||||||
|
icon: "ph-lightning ph-bold ph-lg",
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
searchQuery = getUrlParams()['q'];
|
||||||
|
channel = getUrlParams()['channel'] ?? undefined;
|
||||||
|
|
||||||
|
syncSlide(tabs.indexOf(tab));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function search(query: string) {
|
||||||
|
const q = query.trim();
|
||||||
|
|
||||||
|
if (q.startsWith("@") && !q.includes(" ")) {
|
||||||
|
router.push(`/${q}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith("#")) {
|
||||||
|
router.push(`/tags/${encodeURIComponent(q.slice(1))}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith("http://") || q.startsWith("https://")) {
|
||||||
|
const promise = os.api("ap/show", {
|
||||||
|
uri: q,
|
||||||
|
});
|
||||||
|
|
||||||
|
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
||||||
|
|
||||||
|
const res = await promise;
|
||||||
|
|
||||||
|
if (res.type === "User") {
|
||||||
|
router.push(`/@${res.object.username}@${res.object.host}`);
|
||||||
|
} else if (res.type === "Note") {
|
||||||
|
router.push(`/notes/${res.object.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchQuery = q;
|
||||||
|
router.push(`/discover?q=${encodeURIComponent(q)}`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.buttoncontainer {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfpdbgtk {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
grid-gap: 12px;
|
||||||
|
margin: 0 var(--margin);
|
||||||
|
|
||||||
|
> .post {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rknalgpo {
|
||||||
|
> .buttoncontainer {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.my .ckltabjg:first-child {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckltabjg:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 500px) {
|
||||||
|
.ckltabjg:not(:last-child) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,96 +0,0 @@
|
|||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header
|
|
||||||
><MkPageHeader
|
|
||||||
v-model:tab="tab"
|
|
||||||
:actions="headerActions"
|
|
||||||
:tabs="headerTabs"
|
|
||||||
/></template>
|
|
||||||
<div class="lznhrdub">
|
|
||||||
<MkSpacer :content-max="1200">
|
|
||||||
<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>
|
|
||||||
<XUsers />
|
|
||||||
</swiper-slide>
|
|
||||||
<swiper-slide>
|
|
||||||
<XFeatured />
|
|
||||||
</swiper-slide>
|
|
||||||
</swiper>
|
|
||||||
</MkSpacer>
|
|
||||||
</div>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch, onMounted } from "vue";
|
|
||||||
import { Virtual } from "swiper/modules";
|
|
||||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
|
||||||
import XFeatured from "./explore.featured.vue";
|
|
||||||
import XUsers from "./explore.users.vue";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import "swiper/scss";
|
|
||||||
import "swiper/scss/virtual";
|
|
||||||
|
|
||||||
const tabs = ["users", "featured"];
|
|
||||||
let tab = $ref(tabs[0]);
|
|
||||||
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => [
|
|
||||||
{
|
|
||||||
key: "users",
|
|
||||||
icon: "ph-users ph-bold ph-lg",
|
|
||||||
title: i18n.ts.users,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "featured",
|
|
||||||
icon: "ph-lightning ph-bold ph-lg",
|
|
||||||
title: i18n.ts.featured,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
definePageMetadata(
|
|
||||||
computed(() => ({
|
|
||||||
title: i18n.ts.explore,
|
|
||||||
icon: "ph-hash ph-bold ph-lg",
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header
|
<template #header
|
||||||
><MkPageHeader :actions="headerActions" :tabs="headerTabs"
|
><MkPageHeader :actions="headerActions" :tabs="headerTabs" :display-back-button="true"
|
||||||
/></template>
|
/></template>
|
||||||
<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
|
<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
|
||||||
<FormSuspense :p="init" class="_formRoot">
|
<FormSuspense :p="init" class="_formRoot">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header
|
<template #header
|
||||||
><MkPageHeader :actions="headerActions" :tabs="headerTabs"
|
><MkPageHeader :actions="headerActions" :tabs="headerTabs" :display-back-button="true"
|
||||||
/></template>
|
/></template>
|
||||||
<MkSpacer :content-max="1000" :margin-min="16" :margin-max="32">
|
<MkSpacer :content-max="1000" :margin-min="16" :margin-max="32">
|
||||||
<div class="_root">
|
<div class="_root">
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header
|
|
||||||
><MkPageHeader
|
|
||||||
v-model:tab="tab"
|
|
||||||
:actions="headerActions"
|
|
||||||
:tabs="headerTabs"
|
|
||||||
/></template>
|
|
||||||
<MkSpacer :content-max="800">
|
|
||||||
<MkSearch :query="searchQuery" :hideFilters="!$i || tab === 'users'" @query="search"/>
|
|
||||||
<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>
|
|
||||||
<template v-if="$i">
|
|
||||||
<template v-if="searchQuery == null || searchQuery.trim().length < 1">
|
|
||||||
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
|
|
||||||
<div class="_fullinfo" ref="notes">
|
|
||||||
<img
|
|
||||||
:src="instance.images.info"
|
|
||||||
class="_ghost"
|
|
||||||
alt="Info"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
{{ i18n.ts.searchEmptyQuery }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="tabs[swiperRef!.activeIndex] == 'notes'">
|
|
||||||
<XNotes ref="notes" :pagination="notesPagination" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
|
|
||||||
<div class="_fullinfo" ref="notes">
|
|
||||||
<img
|
|
||||||
:src="instance.images.info"
|
|
||||||
class="_ghost"
|
|
||||||
alt="Info"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
{{ i18n.ts.searchNotLoggedIn_1 }}<br>
|
|
||||||
{{ i18n.ts.searchNotLoggedIn_2 }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
</swiper-slide>
|
|
||||||
<swiper-slide>
|
|
||||||
<template v-if="searchQuery == null || searchQuery.trim().length < 1">
|
|
||||||
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
|
|
||||||
<div class="_fullinfo" ref="notes">
|
|
||||||
<img
|
|
||||||
:src="instance.images.info"
|
|
||||||
class="_ghost"
|
|
||||||
alt="Info"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
{{ i18n.ts.searchEmptyQuery }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="tabs[swiperRef!.activeIndex] == 'users'">
|
|
||||||
<XUserList
|
|
||||||
ref="users"
|
|
||||||
class="_gap"
|
|
||||||
:pagination="usersPagination"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</swiper-slide>
|
|
||||||
</swiper>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch, onMounted, onActivated, ref } from "vue";
|
|
||||||
import { Virtual } from "swiper/modules";
|
|
||||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
|
||||||
import XNotes from "@/components/MkNotes.vue";
|
|
||||||
import XUserList from "@/components/MkUserList.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import "swiper/scss";
|
|
||||||
import "swiper/scss/virtual";
|
|
||||||
import {instance} from "@/instance";
|
|
||||||
import MkSearch from "@/components/MkSearch.vue";
|
|
||||||
import { useRouter } from "@/router.js";
|
|
||||||
import * as os from "@/os.js";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const getUrlParams = () =>
|
|
||||||
window.location.search
|
|
||||||
.substring(1)
|
|
||||||
.split("&")
|
|
||||||
.reduce((result, query) => {
|
|
||||||
const [k, v] = query.split("=");
|
|
||||||
result[k] = decodeURIComponent(v?.replace('+', '%20'));
|
|
||||||
return result;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let searchQuery = $ref<string>(getUrlParams()['q'] ?? "");
|
|
||||||
let channel = $ref<string|undefined>(getUrlParams()['channel'] ?? undefined);
|
|
||||||
|
|
||||||
const notesPagination = {
|
|
||||||
endpoint: "notes/search" as const,
|
|
||||||
limit: 10,
|
|
||||||
params: computed(() => ({
|
|
||||||
query: searchQuery,
|
|
||||||
channelId: channel,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
const usersPagination = {
|
|
||||||
endpoint: "users/search" as const,
|
|
||||||
limit: 10,
|
|
||||||
params: computed(() => ({
|
|
||||||
query: searchQuery,
|
|
||||||
origin: "combined",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabs = ["notes", "users"];
|
|
||||||
let tab = $ref(tabs[0]);
|
|
||||||
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => [
|
|
||||||
{
|
|
||||||
key: "notes",
|
|
||||||
icon: "ph-magnifying-glass ph-bold ph-lg",
|
|
||||||
title: i18n.ts.notes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "users",
|
|
||||||
icon: "ph-users ph-bold ph-lg",
|
|
||||||
title: i18n.ts.users,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
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(tab));
|
|
||||||
});
|
|
||||||
|
|
||||||
onActivated(() => {
|
|
||||||
searchQuery = getUrlParams()['q'];
|
|
||||||
channel = getUrlParams()['channel'] ?? undefined;
|
|
||||||
|
|
||||||
syncSlide(tabs.indexOf(tab));
|
|
||||||
});
|
|
||||||
|
|
||||||
definePageMetadata(
|
|
||||||
computed(() => ({
|
|
||||||
title: i18n.ts.search,
|
|
||||||
icon: "ph-magnifying-glass ph-bold ph-lg",
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
async function search(query: string) {
|
|
||||||
const q = query.trim();
|
|
||||||
|
|
||||||
if (q.startsWith("@") && !q.includes(" ")) {
|
|
||||||
router.push(`/${q}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (q.startsWith("#")) {
|
|
||||||
router.push(`/tags/${encodeURIComponent(q.slice(1))}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (q.startsWith("http://") || q.startsWith("https://")) {
|
|
||||||
const promise = os.api("ap/show", {
|
|
||||||
uri: q,
|
|
||||||
});
|
|
||||||
|
|
||||||
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
|
||||||
|
|
||||||
const res = await promise;
|
|
||||||
|
|
||||||
if (res.type === "User") {
|
|
||||||
router.push(`/@${res.object.username}@${res.object.host}`);
|
|
||||||
} else if (res.type === "Note") {
|
|
||||||
router.push(`/notes/${res.object.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchQuery = q;
|
|
||||||
router.push(`/search?q=${encodeURIComponent(q)}`);
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -337,16 +337,12 @@ export const routes = [
|
|||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/explore/tags/:tag",
|
path: "/discover/tags/:tag",
|
||||||
component: page(() => import("./pages/explore.vue")),
|
component: page(() => import("./pages/discover.vue")),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/explore",
|
path: "/discover",
|
||||||
component: page(() => import("./pages/explore.vue")),
|
component: page(() => import("./pages/discover.vue")),
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/search",
|
|
||||||
component: page(() => import("./pages/search.vue")),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/authorize-follow",
|
path: "/authorize-follow",
|
||||||
|
@ -15,8 +15,7 @@ const menuOptions = [
|
|||||||
"-",
|
"-",
|
||||||
"snippets",
|
"snippets",
|
||||||
"antennas",
|
"antennas",
|
||||||
"explore",
|
"discover",
|
||||||
"search",
|
|
||||||
"-",
|
"-",
|
||||||
"achievements",
|
"achievements",
|
||||||
"drive"
|
"drive"
|
||||||
@ -153,7 +152,6 @@ export const defaultStore = markRaw(
|
|||||||
arg: null,
|
arg: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
overridedDeviceKind: {
|
overridedDeviceKind: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: null as null | "smartphone" | "tablet" | "desktop",
|
default: null as null | "smartphone" | "tablet" | "desktop",
|
||||||
|
@ -41,9 +41,9 @@
|
|||||||
><i class="ph-house ph-bold ph-lg icon"></i
|
><i class="ph-house ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.home }}</MkA
|
>{{ i18n.ts.home }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/explore" class="link" active-class="active"
|
<MkA to="/discover" class="link" active-class="active"
|
||||||
><i class="ph-compass ph-bold ph-lg icon"></i
|
><i class="ph-lightning ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.explore }}</MkA
|
>{{ i18n.ts.discover }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/pages" class="link" active-class="active"
|
<MkA to="/pages" class="link" active-class="active"
|
||||||
><i class="ph-file-text ph-bold ph-lg icon"></i
|
><i class="ph-file-text ph-bold ph-lg icon"></i
|
||||||
@ -53,10 +53,6 @@
|
|||||||
><i class="ph-image-square ph-bold ph-lg icon"></i
|
><i class="ph-image-square ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.gallery }}</MkA
|
>{{ i18n.ts.gallery }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/search" class="link" active-class="active">
|
|
||||||
<i class="ph-magnifying-glass ph-bold ph-lg icon"></i
|
|
||||||
><span>{{ i18n.ts.search }}</span>
|
|
||||||
</MkA>
|
|
||||||
<MkA to="/settings" class="link" active-class="active">
|
<MkA to="/settings" class="link" active-class="active">
|
||||||
<i class="ph-gear-six ph-bold ph-lg icon"></i
|
<i class="ph-gear-six ph-bold ph-lg icon"></i
|
||||||
><span>{{ i18n.ts.settings }}</span>
|
><span>{{ i18n.ts.settings }}</span>
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
><i class="ph-chats-circle ph-bold ph-lg icon"></i
|
><i class="ph-chats-circle ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.timeline }}</MkA
|
>{{ i18n.ts.timeline }}</MkA
|
||||||
> -->
|
> -->
|
||||||
<MkA to="/explore" class="link" active-class="active"
|
<MkA to="/discover" class="link" active-class="active"
|
||||||
><i class="ph-compass ph-bold ph-lg icon"></i
|
><i class="ph-lightning ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.explore }}</MkA
|
>{{ i18n.ts.discover }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/pages" class="link" active-class="active"
|
<MkA to="/pages" class="link" active-class="active"
|
||||||
><i class="ph-file-text ph-bold ph-lg icon"></i
|
><i class="ph-file-text ph-bold ph-lg icon"></i
|
||||||
@ -26,10 +26,6 @@
|
|||||||
><i class="ph-image-square ph-bold ph-lg icon"></i
|
><i class="ph-image-square ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.gallery }}</MkA
|
>{{ i18n.ts.gallery }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/search" class="link" active-class="active">
|
|
||||||
<i class="ph-magnifying-glass ph-bold ph-lg icon"></i
|
|
||||||
><span>{{ i18n.ts.search }}</span>
|
|
||||||
</MkA>
|
|
||||||
<div v-if="info" class="page active link">
|
<div v-if="info" class="page active link">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i v-if="info.icon" class="icon" :class="info.icon"></i>
|
<i v-if="info.icon" class="icon" :class="info.icon"></i>
|
||||||
|
Loading…
Reference in New Issue
Block a user