summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages
diff options
context:
space:
mode:
authorChocolate Pie <106949016+chocolate-pie@users.noreply.github.com>2023-05-19 10:06:12 +0900
committerGitHub <noreply@github.com>2023-05-19 10:06:12 +0900
commitdddbc1c894f53d6891ca7760dd9382c19931661a (patch)
treeedc57ec92a68d547c9b31e5aa139dcf61ba5095a /packages/frontend/src/pages
parentperf: MkImgWithBlurhashとMkMediaImageを最適化 (#10782) (diff)
downloadmisskey-dddbc1c894f53d6891ca7760dd9382c19931661a.tar.gz
misskey-dddbc1c894f53d6891ca7760dd9382c19931661a.tar.bz2
misskey-dddbc1c894f53d6891ca7760dd9382c19931661a.zip
feat: 公開リスト (#10842)
* feat: まず公開できるように (misskey-dev/misskey#10447) * feat: 公開したリストのページを作成 (misskey-dev/misskey#10447) * feat: いいねできるように * feat: インポートに対応 * wip * wip * CHANGELOGを編集 * add note * refactor --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/frontend/src/pages')
-rw-r--r--packages/frontend/src/pages/list.vue148
-rw-r--r--packages/frontend/src/pages/my-lists/index.vue1
-rw-r--r--packages/frontend/src/pages/my-lists/list.vue90
-rw-r--r--packages/frontend/src/pages/user/index.vue6
-rw-r--r--packages/frontend/src/pages/user/lists.vue51
5 files changed, 257 insertions, 39 deletions
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
new file mode 100644
index 0000000000..a09ff854ca
--- /dev/null
+++ b/packages/frontend/src/pages/list.vue
@@ -0,0 +1,148 @@
+<template>
+<MkStickyContainer>
+ <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+ <MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200">
+ <div :class="$style.root">
+ <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
+ <p :class="$style.text">
+ <i class="ti ti-alert-triangle"></i>
+ {{ i18n.ts.nothing }}
+ </p>
+ </div>
+ </MKSpacer>
+ <MkSpacer v-else-if="list" :content-max="700" :class="$style.main">
+ <div v-if="list" class="members _margin">
+ <div :class="$style.member_text">{{ i18n.ts.members }}</div>
+ <div class="_gaps_s">
+ <div v-for="user in users" :key="user.id" :class="$style.userItem">
+ <MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+ <MkUserCardMini :user="user"/>
+ </MkA>
+ </div>
+ </div>
+ </div>
+ <MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" as-like primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton>
+ <MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" as-like @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
+ <MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
+ </MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { watch, computed } from 'vue';
+import * as os from '@/os';
+import { userPage } from '@/filters/user';
+import { i18n } from '@/i18n';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkButton from '@/components/MkButton.vue';
+import { definePageMetadata } from '@/scripts/page-metadata';
+
+const props = defineProps<{
+ listId: string;
+}>();
+
+let list = $ref(null);
+let error = $ref();
+let users = $ref([]);
+
+function fetchList(): void {
+ os.api('users/lists/show', {
+ listId: props.listId,
+ forPublic: true,
+ }).then(_list => {
+ list = _list;
+ os.api('users/show', {
+ userIds: list.userIds,
+ }).then(_users => {
+ users = _users;
+ });
+ }).catch(err => {
+ error = err;
+ });
+}
+
+function like() {
+ os.apiWithDialog('users/lists/favorite', {
+ listId: list.id,
+ }).then(() => {
+ list.isLiked = true;
+ list.likedCount++;
+ });
+}
+
+function unlike() {
+ os.apiWithDialog('users/lists/unfavorite', {
+ listId: list.id,
+ }).then(() => {
+ list.isLiked = false;
+ list.likedCount--;
+ });
+}
+
+async function create() {
+ const { canceled, result: name } = await os.inputText({
+ title: i18n.ts.enterListName,
+ });
+ if (canceled) return;
+ await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.id });
+}
+
+watch(() => props.listId, fetchList, { immediate: true });
+
+const headerActions = $computed(() => []);
+
+const headerTabs = $computed(() => []);
+
+definePageMetadata(computed(() => list ? {
+ title: list.name,
+ icon: 'ti ti-list',
+} : null));
+</script>
+<style lang="scss" module>
+.main {
+ min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+}
+
+.userItem {
+ display: flex;
+}
+
+.userItemBody {
+ flex: 1;
+ min-width: 0;
+ margin-right: 8px;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+.member_text {
+ margin: 5px;
+}
+
+.root {
+ padding: 32px;
+ text-align: center;
+ align-items: center;
+}
+
+.text {
+ margin: 0 0 8px 0;
+}
+
+.img {
+ vertical-align: bottom;
+ width: 128px;
+ height: 128px;
+ margin-bottom: 16px;
+ border-radius: 16px;
+}
+
+.button {
+ margin-right: 10px;
+}
+
+.import {
+ margin-right: 4px;
+}
+</style>
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 47437f3e57..6068e375ea 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -70,6 +70,7 @@ definePageMetadata({
padding: 16px;
border: solid 1px var(--divider);
border-radius: 6px;
+ margin-bottom: 8px;
&:hover {
border: solid 1px var(--accent);
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 86201e8e0c..dd431e8dc0 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -1,35 +1,43 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :content-max="700" :class="$style.main">
- <div v-if="list" class="members _margin">
- <div class="">{{ i18n.ts.members }}</div>
- <div class="_gaps_s">
- <div v-for="user in users" :key="user.id" :class="$style.userItem">
- <MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
- <MkUserCardMini :user="user"/>
- </MkA>
- <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
+ <MkSpacer :contentMax="700" :class="$style.main">
+ <div v-if="list" class="_gaps">
+ <MkFolder>
+ <template #label>{{ i18n.ts.settings }}</template>
+
+ <div class="_gaps">
+ <MkInput v-model="name">
+ <template #label>{{ i18n.ts.name }}</template>
+ </MkInput>
+ <MkSwitch v-model="isPublic">{{ i18n.ts.public }}</MkSwitch>
+ <div class="_buttons">
+ <MkButton rounded primary @click="updateSettings">{{ i18n.ts.save }}</MkButton>
+ <MkButton rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
+ </div>
</div>
- </div>
- </div>
- </MkSpacer>
- <template #footer>
- <div :class="$style.footer">
- <MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
- <div class="_buttons">
- <MkButton inline rounded primary @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
- <MkButton inline rounded @click="renameList()">{{ i18n.ts.rename }}</MkButton>
- <MkButton inline rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
+ </MkFolder>
+
+ <MkFolder defaultOpen>
+ <template #label>{{ i18n.ts.members }}</template>
+
+ <div class="_gaps_s">
+ <MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
+ <div v-for="user in users" :key="user.id" :class="$style.userItem">
+ <MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+ <MkUserCardMini :user="user"/>
+ </MkA>
+ <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
+ </div>
</div>
- </MkSpacer>
+ </MkFolder>
</div>
- </template>
+ </MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { mainRouter } from '@/router';
@@ -37,6 +45,9 @@ import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n';
import { userPage } from '@/filters/user';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkInput from '@/components/MkInput.vue';
import { userListsCache } from '@/cache';
const props = defineProps<{
@@ -45,12 +56,17 @@ const props = defineProps<{
let list = $ref(null);
let users = $ref([]);
+const isPublic = ref(false);
+const name = ref('');
function fetchList() {
os.api('users/lists/show', {
listId: props.listId,
}).then(_list => {
list = _list;
+ name.value = list.name;
+ isPublic.value = list.isPublic;
+
os.api('users/show', {
userIds: list.userIds,
}).then(_users => {
@@ -86,23 +102,6 @@ async function removeUser(user, ev) {
}], ev.currentTarget ?? ev.target);
}
-async function renameList() {
- const { canceled, result: name } = await os.inputText({
- title: i18n.ts.enterListName,
- default: list.name,
- });
- if (canceled) return;
-
- await os.api('users/lists/update', {
- listId: list.id,
- name: name,
- });
-
- userListsCache.delete();
-
- list.name = name;
-}
-
async function deleteList() {
const { canceled } = await os.confirm({
type: 'warning',
@@ -117,6 +116,19 @@ async function deleteList() {
mainRouter.push('/my/lists');
}
+async function updateSettings() {
+ await os.apiWithDialog('users/lists/update', {
+ listId: list.id,
+ name: name.value,
+ isPublic: isPublic.value,
+ });
+
+ userListsCache.delete();
+
+ list.name = name.value;
+ list.isPublic = isPublic.value;
+}
+
watch(() => props.listId, fetchList, { immediate: true });
const headerActions = $computed(() => []);
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 03a226cc09..07f7d30f0c 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -10,6 +10,7 @@
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
<XClips v-else-if="tab === 'clips'" :user="user"/>
+ <XLists v-else-if="tab === 'lists'" :user="user"/>
<XPages v-else-if="tab === 'pages'" :user="user"/>
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
</div>
@@ -36,6 +37,7 @@ const XActivity = defineAsyncComponent(() => import('./activity.vue'));
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
const XReactions = defineAsyncComponent(() => import('./reactions.vue'));
const XClips = defineAsyncComponent(() => import('./clips.vue'));
+const XLists = defineAsyncComponent(() => import('./lists.vue'));
const XPages = defineAsyncComponent(() => import('./pages.vue'));
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
@@ -91,6 +93,10 @@ const headerTabs = $computed(() => user ? [{
title: i18n.ts.clips,
icon: 'ti ti-paperclip',
}, {
+ key: 'lists',
+ title: i18n.ts.lists,
+ icon: 'ti ti-list',
+}, {
key: 'pages',
title: i18n.ts.pages,
icon: 'ti ti-news',
diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue
new file mode 100644
index 0000000000..78f03d2b38
--- /dev/null
+++ b/packages/frontend/src/pages/user/lists.vue
@@ -0,0 +1,51 @@
+<template>
+<MkStickyContainer>
+ <MkSpacer :contentMax="700">
+ <div>
+ <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists">
+ <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
+ <div>{{ list.name }}</div>
+ <MkAvatars :userIds="list.userIds"/>
+ </MkA>
+ </MkPagination>
+ </div>
+ </MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import {} from 'vue';
+import * as misskey from 'misskey-js';
+import MkPagination from '@/components/MkPagination.vue';
+import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
+import MkSpacer from '@/components/global/MkSpacer.vue';
+import MkAvatars from '@/components/MkAvatars.vue';
+
+const props = defineProps<{
+ user: misskey.entities.UserDetailed;
+}>();
+
+const pagination = {
+ endpoint: 'users/lists/list' as const,
+ noPaging: true,
+ limit: 10,
+ params: {
+ userId: props.user.id,
+ },
+};
+</script>
+
+<style lang="scss" module>
+.list {
+ display: block;
+ padding: 16px;
+ border: solid 1px var(--divider);
+ border-radius: 6px;
+ margin-bottom: 8px;
+
+ &:hover {
+ border: solid 1px var(--accent);
+ text-decoration: none;
+ }
+}
+</style>