diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-04-24 23:04:59 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-04-24 23:04:59 +0900 |
| commit | 8043409d386d5e08c85d27c720ecca2b3f8030ab (patch) | |
| tree | 584bc43b126dbdc9ba592758aa9c17f8e122f344 /src/client/pages | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.79.0 (diff) | |
| download | misskey-8043409d386d5e08c85d27c720ecca2b3f8030ab.tar.gz misskey-8043409d386d5e08c85d27c720ecca2b3f8030ab.tar.bz2 misskey-8043409d386d5e08c85d27c720ecca2b3f8030ab.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/client/pages')
| -rw-r--r-- | src/client/pages/gallery/index.vue | 152 | ||||
| -rw-r--r-- | src/client/pages/gallery/new.vue | 110 | ||||
| -rw-r--r-- | src/client/pages/gallery/post.vue | 271 | ||||
| -rw-r--r-- | src/client/pages/page.vue | 3 | ||||
| -rw-r--r-- | src/client/pages/timeline.vue | 2 | ||||
| -rw-r--r-- | src/client/pages/user/gallery.vue | 63 | ||||
| -rw-r--r-- | src/client/pages/user/index.vue | 15 |
7 files changed, 609 insertions, 7 deletions
diff --git a/src/client/pages/gallery/index.vue b/src/client/pages/gallery/index.vue new file mode 100644 index 0000000000..9e726e70f2 --- /dev/null +++ b/src/client/pages/gallery/index.vue @@ -0,0 +1,152 @@ +<template> +<div class="xprsixdl _root"> + <MkTab v-model:value="tab" v-if="$i"> + <option value="explore"><i class="fas fa-icons"></i> {{ $ts.gallery }}</option> + <option value="liked"><i class="fas fa-heart"></i> {{ $ts._gallery.liked }}</option> + <option value="my"><i class="fas fa-edit"></i> {{ $ts._gallery.my }}</option> + </MkTab> + + <div v-if="tab === 'explore'"> + <MkFolder class="_gap"> + <template #header><i class="fas fa-clock"></i>{{ $ts.recentPosts }}</template> + <MkPagination :pagination="recentPostsPagination" #default="{items}" :disable-auto-load="true"> + <div class="vfpdbgtk"> + <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/> + </div> + </MkPagination> + </MkFolder> + <MkFolder class="_gap"> + <template #header><i class="fas fa-fire-alt"></i>{{ $ts.popularPosts }}</template> + <MkPagination :pagination="popularPostsPagination" #default="{items}" :disable-auto-load="true"> + <div class="vfpdbgtk"> + <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/> + </div> + </MkPagination> + </MkFolder> + </div> + <div v-else-if="tab === 'liked'"> + <MkPagination :pagination="likedPostsPagination" #default="{items}"> + <div class="vfpdbgtk"> + <MkGalleryPostPreview v-for="like in items" :post="like.post" :key="like.id" class="post"/> + </div> + </MkPagination> + </div> + <div v-else-if="tab === 'my'"> + <MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="fas fa-plus"></i> {{ $ts.postToGallery }}</MkA> + <MkPagination :pagination="myPostsPagination" #default="{items}"> + <div class="vfpdbgtk"> + <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/> + </div> + </MkPagination> + </div> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent } from 'vue'; +import XUserList from '@client/components/user-list.vue'; +import MkFolder from '@client/components/ui/folder.vue'; +import MkInput from '@client/components/ui/input.vue'; +import MkButton from '@client/components/ui/button.vue'; +import MkTab from '@client/components/tab.vue'; +import MkPagination from '@client/components/ui/pagination.vue'; +import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue'; +import number from '@client/filters/number'; +import * as os from '@client/os'; +import * as symbols from '@client/symbols'; + +export default defineComponent({ + components: { + XUserList, + MkFolder, + MkInput, + MkButton, + MkTab, + MkPagination, + MkGalleryPostPreview, + }, + + props: { + tag: { + type: String, + required: false + } + }, + + data() { + return { + [symbols.PAGE_INFO]: { + title: this.$ts.gallery, + icon: 'fas fa-icons' + }, + tab: 'explore', + recentPostsPagination: { + endpoint: 'gallery/posts', + limit: 6, + }, + popularPostsPagination: { + endpoint: 'gallery/featured', + limit: 5, + }, + myPostsPagination: { + endpoint: 'i/gallery/posts', + limit: 5, + }, + likedPostsPagination: { + endpoint: 'i/gallery/likes', + limit: 5, + }, + tags: [], + }; + }, + + computed: { + meta() { + return this.$instance; + }, + tagUsers(): any { + return { + endpoint: 'hashtags/users', + limit: 30, + params: { + tag: this.tag, + origin: 'combined', + sort: '+follower', + } + }; + }, + }, + + watch: { + tag() { + if (this.$refs.tags) this.$refs.tags.toggleContent(this.tag == null); + }, + }, + + created() { + + }, + + methods: { + + } +}); +</script> + +<style lang="scss" scoped> +.xprsixdl { + max-width: 1400px; + margin: 0 auto; +} + +.vfpdbgtk { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: 12px; + margin: 0 var(--margin); + + > .post { + + } +} +</style> diff --git a/src/client/pages/gallery/new.vue b/src/client/pages/gallery/new.vue new file mode 100644 index 0000000000..3f9756df8e --- /dev/null +++ b/src/client/pages/gallery/new.vue @@ -0,0 +1,110 @@ +<template> +<FormBase> + <FormInput v-model:value="title"> + <span>{{ $ts.title }}</span> + </FormInput> + + <FormTextarea v-model:value="description" :max="500"> + <span>{{ $ts.description }}</span> + </FormTextarea> + + <FormGroup> + <div v-for="file in files" :key="file.id" class="_formItem _formPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> + <div class="name">{{ file.name }}</div> + <button class="remove _button" @click="remove(file)" v-tooltip="$ts.remove"><i class="fas fa-times"></i></button> + </div> + <FormButton @click="selectFile" primary><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton> + </FormGroup> + + <FormSwitch v-model:value="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch> + + <FormButton @click="publish" primary><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton> +</FormBase> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import FormButton from '@client/components/form/button.vue'; +import FormInput from '@client/components/form/input.vue'; +import FormTextarea from '@client/components/form/textarea.vue'; +import FormSwitch from '@client/components/form/switch.vue'; +import FormTuple from '@client/components/form/tuple.vue'; +import FormBase from '@client/components/form/base.vue'; +import FormGroup from '@client/components/form/group.vue'; +import { selectFile } from '@client/scripts/select-file'; +import * as os from '@client/os'; +import * as symbols from '@client/symbols'; + +export default defineComponent({ + components: { + FormButton, + FormInput, + FormTextarea, + FormSwitch, + FormBase, + FormGroup, + }, + + data() { + return { + [symbols.PAGE_INFO]: { + title: this.$ts.postToGallery, + icon: 'fas fa-pencil-alt' + }, + files: [], + description: null, + title: null, + isSensitive: false, + } + }, + + methods: { + selectFile(e) { + selectFile(e.currentTarget || e.target, null, true).then(files => { + this.files = this.files.concat(files); + }); + }, + + remove(file) { + this.files = this.files.filter(f => f.id !== file.id); + }, + + async publish() { + const post = await os.apiWithDialog('gallery/posts/create', { + title: this.title, + description: this.description, + fileIds: this.files.map(file => file.id), + isSensitive: this.isSensitive, + }); + + this.$router.push(`/gallery/${post.id}`); + } + } +}); +</script> + +<style lang="scss" scoped> +.wqugxsfx { + height: 200px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + position: relative; + + > .name { + position: absolute; + top: 8px; + left: 9px; + padding: 8px; + background: var(--panel); + } + + > .remove { + position: absolute; + top: 8px; + right: 9px; + padding: 8px; + background: var(--panel); + } +} +</style> diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue new file mode 100644 index 0000000000..86fae99888 --- /dev/null +++ b/src/client/pages/gallery/post.vue @@ -0,0 +1,271 @@ +<template> +<div class="_root"> + <transition name="fade" mode="out-in"> + <div v-if="post" class="rkxwuolj"> + <div class="files"> + <div class="file" v-for="file in post.files" :key="file.id"> + <img :src="file.url"/> + </div> + </div> + <div class="body _block"> + <div class="title">{{ post.title }}</div> + <div class="description"><Mfm :text="post.description"/></div> + <div class="info"> + <i class="fas fa-clock"></i> <MkTime :time="post.createdAt" mode="detail"/> + </div> + <div class="actions"> + <div class="like"> + <MkButton class="button" @click="unlike()" v-if="post.isLiked" v-tooltip="$ts._gallery.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="post.likedCount > 0">{{ post.likedCount }}</span></MkButton> + <MkButton class="button" @click="like()" v-else v-tooltip="$ts._gallery.like"><i class="far fa-heart"></i><span class="count" v-if="post.likedCount > 0">{{ post.likedCount }}</span></MkButton> + </div> + <div class="other"> + <button class="_button" @click="createNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button> + <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button> + </div> + </div> + <div class="user"> + <MkAvatar :user="post.user" class="avatar"/> + <div class="name"> + <MkUserName :user="post.user" style="display: block;"/> + <MkAcct :user="post.user"/> + </div> + <MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> + </div> + </div> + <MkContainer :max-height="300" :foldable="true" class="other"> + <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> + <MkPagination :pagination="otherPostsPagination" #default="{items}"> + <div class="sdrarzaf"> + <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/> + </div> + </MkPagination> + </MkContainer> + </div> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent } from 'vue'; +import MkButton from '@client/components/ui/button.vue'; +import * as os from '@client/os'; +import * as symbols from '@client/symbols'; +import MkContainer from '@client/components/ui/container.vue'; +import ImgWithBlurhash from '@client/components/img-with-blurhash.vue'; +import MkPagination from '@client/components/ui/pagination.vue'; +import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue'; +import MkFollowButton from '@client/components/follow-button.vue'; +import { url } from '@client/config'; + +export default defineComponent({ + components: { + MkContainer, + ImgWithBlurhash, + MkPagination, + MkGalleryPostPreview, + MkButton, + MkFollowButton, + }, + props: { + postId: { + type: String, + required: true + } + }, + data() { + return { + [symbols.PAGE_INFO]: computed(() => this.post ? { + title: this.post.title, + avatar: this.post.user, + path: `/gallery/${this.post.id}`, + share: { + title: this.post.title, + text: this.post.description, + }, + } : null), + otherPostsPagination: { + endpoint: 'users/gallery/posts', + limit: 6, + params: () => ({ + userId: this.post.user.id + }) + }, + post: null, + error: null, + }; + }, + + watch: { + postId: 'fetch' + }, + + created() { + this.fetch(); + }, + + methods: { + fetch() { + this.post = null; + os.api('gallery/posts/show', { + postId: this.postId + }).then(post => { + this.post = post; + }).catch(e => { + this.error = e; + }); + }, + + share() { + navigator.share({ + title: this.post.title, + text: this.post.description, + url: `${url}/gallery/${this.post.id}` + }); + }, + + like() { + os.apiWithDialog('gallery/posts/like', { + postId: this.postId, + }).then(() => { + this.post.isLiked = true; + this.post.likedCount++; + }); + }, + + async unlike() { + const confirm = await os.dialog({ + type: 'warning', + showCancelButton: true, + text: this.$ts.unlikeConfirm, + }); + if (confirm.canceled) return; + os.apiWithDialog('gallery/posts/unlike', { + postId: this.postId, + }).then(() => { + this.post.isLiked = false; + this.post.likedCount--; + }); + }, + + createNote() { + os.post({ + initialText: `${this.post.title} ${url}/gallery/${this.post.id}` + }); + } + } +}); +</script> + +<style lang="scss" scoped> +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.125s ease; +} +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.rkxwuolj { + > .files { + > .file { + > img { + display: block; + max-width: 100%; + max-height: 500px; + margin: 0 auto; + } + + & + .file { + margin-top: 16px; + } + } + } + + > .body { + padding: 32px; + + > .title { + font-weight: bold; + font-size: 1.2em; + margin-bottom: 16px; + } + + > .info { + margin-top: 16px; + font-size: 90%; + opacity: 0.7; + } + + > .actions { + display: flex; + align-items: center; + margin-top: 16px; + padding: 16px 0 0 0; + border-top: solid 0.5px var(--divider); + + > .like { + > .button { + --accent: rgb(241 97 132); + --X8: rgb(241 92 128); + --buttonBg: rgb(216 71 106 / 5%); + --buttonHoverBg: rgb(216 71 106 / 10%); + color: #ff002f; + + ::v-deep(.count) { + margin-left: 0.5em; + } + } + } + + > .other { + margin-left: auto; + + > button { + padding: 8px; + margin: 0 8px; + + &:hover { + color: var(--fgHighlighted); + } + } + } + } + + > .user { + margin-top: 16px; + padding: 16px 0 0 0; + border-top: solid 0.5px var(--divider); + display: flex; + align-items: center; + + > .avatar { + width: 52px; + height: 52px; + } + + > .name { + margin: 0 0 0 12px; + font-size: 90%; + } + + > .koudoku { + margin-left: auto; + } + } + } +} + +.sdrarzaf { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: 12px; + margin: var(--margin); + + > .post { + + } +} +</style> diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue index f25ed51184..e43add7b0b 100644 --- a/src/client/pages/page.vue +++ b/src/client/pages/page.vue @@ -166,10 +166,11 @@ export default defineComponent({ border-top: solid 0.5px var(--divider); > .button { - --accent: rgb(216 71 106); + --accent: rgb(241 97 132); --X8: rgb(241 92 128); --buttonBg: rgb(216 71 106 / 5%); --buttonHoverBg: rgb(216 71 106 / 10%); + color: #ff002f; ::v-deep(.count) { margin-left: 0.5em; diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue index 5660d0099e..966146d92b 100644 --- a/src/client/pages/timeline.vue +++ b/src/client/pages/timeline.vue @@ -18,6 +18,7 @@ <button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><i class="fas fa-list-ul"></i></button> </div> </div> + <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> <XTimeline ref="tl" class="_gap" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src" @@ -30,7 +31,6 @@ @after="after()" @queue="queueUpdated" /> - <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> </div> </template> diff --git a/src/client/pages/user/gallery.vue b/src/client/pages/user/gallery.vue new file mode 100644 index 0000000000..2a4c4e03f4 --- /dev/null +++ b/src/client/pages/user/gallery.vue @@ -0,0 +1,63 @@ +<template> +<div> + <MkPagination :pagination="pagination" #default="{items}"> + <div class="jrnovfpt"> + <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/> + </div> + </MkPagination> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue'; +import MkPagination from '@client/components/ui/pagination.vue'; +import { userPage, acct } from '../../filters/user'; + +export default defineComponent({ + components: { + MkPagination, + MkGalleryPostPreview, + }, + + props: { + user: { + type: Object, + required: true + }, + }, + + data() { + return { + pagination: { + endpoint: 'users/gallery/posts', + limit: 6, + params: () => ({ + userId: this.user.id + }) + }, + }; + }, + + watch: { + user() { + this.$refs.list.reload(); + } + }, + + methods: { + userPage, + + acct + } +}); +</script> + +<style lang="scss" scoped> +.jrnovfpt { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: 12px; + margin: var(--margin); +} +</style> diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index 207b44f631..a1fe7ec09f 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -179,18 +179,22 @@ <div class="contents"> <div class="nav _gap"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link" v-click-anime> <i class="fas fa-comment-alt icon"></i> <span>{{ $ts.notes }}</span> </MkA> - <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link"> + <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link" v-click-anime> <i class="fas fa-paperclip icon"></i> <span>{{ $ts.clips }}</span> </MkA> - <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link"> + <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link" v-click-anime> <i class="fas fa-file-alt icon"></i> <span>{{ $ts.pages }}</span> </MkA> + <MkA :to="userPage(user, 'gallery')" :class="{ active: page === 'gallery' }" class="link" v-click-anime> + <i class="fas fa-icons icon"></i> + <span>{{ $ts.gallery }}</span> + </MkA> </div> <template v-if="page === 'index'"> @@ -210,6 +214,7 @@ <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> </div> </div> <MkError v-else-if="error" @retry="fetch()"/> @@ -250,6 +255,7 @@ export default defineComponent({ XFollowList: defineAsyncComponent(() => import('./follow-list.vue')), XClips: defineAsyncComponent(() => import('./clips.vue')), XPages: defineAsyncComponent(() => import('./pages.vue')), + XGallery: defineAsyncComponent(() => import('./gallery.vue')), XPhotos: defineAsyncComponent(() => import('./index.photos.vue')), XActivity: defineAsyncComponent(() => import('./index.activity.vue')), }, @@ -770,8 +776,7 @@ export default defineComponent({ > .nav { display: flex; align-items: center; - //font-size: 120%; - font-weight: bold; + font-size: 90%; > .link { flex: 1; |