diff options
| author | Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com> | 2023-05-19 10:06:12 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-19 10:06:12 +0900 |
| commit | dddbc1c894f53d6891ca7760dd9382c19931661a (patch) | |
| tree | edc57ec92a68d547c9b31e5aa139dcf61ba5095a /packages/frontend/src/pages | |
| parent | perf: MkImgWithBlurhashとMkMediaImageを最適化 (#10782) (diff) | |
| download | misskey-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.vue | 148 | ||||
| -rw-r--r-- | packages/frontend/src/pages/my-lists/index.vue | 1 | ||||
| -rw-r--r-- | packages/frontend/src/pages/my-lists/list.vue | 90 | ||||
| -rw-r--r-- | packages/frontend/src/pages/user/index.vue | 6 | ||||
| -rw-r--r-- | packages/frontend/src/pages/user/lists.vue | 51 |
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> |