diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-04-08 02:30:37 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-04-08 02:30:37 +0900 |
| commit | a1b490afa756a71b9cef4afa424575bc223bc612 (patch) | |
| tree | 06de4d839e17b1e08e0891542af7360c701a154a /src/client | |
| parent | Merge pull request #1392 from syuilo/greenkeeper/element-ui-2.3.3 (diff) | |
| download | misskey-a1b490afa756a71b9cef4afa424575bc223bc612.tar.gz misskey-a1b490afa756a71b9cef4afa424575bc223bc612.tar.bz2 misskey-a1b490afa756a71b9cef4afa424575bc223bc612.zip | |
Post --> Note
Closes #1411
Diffstat (limited to 'src/client')
88 files changed, 3305 insertions, 912 deletions
diff --git a/src/client/app/auth/views/form.vue b/src/client/app/auth/views/form.vue index eb55b9035b..b323907eb0 100644 --- a/src/client/app/auth/views/form.vue +++ b/src/client/app/auth/views/form.vue @@ -16,7 +16,7 @@ <template v-for="p in app.permission"> <li v-if="p == 'account-read'">アカウントの情報を見る。</li> <li v-if="p == 'account-write'">アカウントの情報を操作する。</li> - <li v-if="p == 'post-write'">投稿する。</li> + <li v-if="p == 'note-write'">投稿する。</li> <li v-if="p == 'like-write'">いいねしたりいいね解除する。</li> <li v-if="p == 'following-write'">フォローしたりフォロー解除する。</li> <li v-if="p == 'drive-read'">ドライブを見る。</li> diff --git a/src/client/app/ch/tags/channel.tag b/src/client/app/ch/tags/channel.tag index 4856728dec..c0561c9b92 100644 --- a/src/client/app/ch/tags/channel.tag +++ b/src/client/app/ch/tags/channel.tag @@ -15,11 +15,11 @@ </div> <div class="body"> - <p v-if="postsFetching">読み込み中<mk-ellipsis/></p> - <div v-if="!postsFetching"> - <p v-if="posts == null || posts.length == 0">まだ投稿がありません</p> - <template v-if="posts != null"> - <mk-channel-post each={ post in posts.slice().reverse() } post={ post } form={ parent.refs.form }/> + <p v-if="notesFetching">読み込み中<mk-ellipsis/></p> + <div v-if="!notesFetching"> + <p v-if="notes == null || notes.length == 0">まだ投稿がありません</p> + <template v-if="notes != null"> + <mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/> </template> </div> </div> @@ -62,9 +62,9 @@ this.id = this.opts.id; this.fetching = true; - this.postsFetching = true; + this.notesFetching = true; this.channel = null; - this.posts = null; + this.notes = null; this.connection = new ChannelStream(this.id); this.unreadCount = 0; @@ -95,9 +95,9 @@ }); // 投稿読み込み - this.$root.$data.os.api('channels/posts', { + this.$root.$data.os.api('channels/notes', { channelId: this.id - }).then(posts => { + }).then(notes => { if (fetched) { Progress.done(); } else { @@ -106,26 +106,26 @@ } this.update({ - postsFetching: false, - posts: posts + notesFetching: false, + notes: notes }); }); - this.connection.on('post', this.onPost); + this.connection.on('note', this.onNote); document.addEventListener('visibilitychange', this.onVisibilitychange, false); }); this.on('unmount', () => { - this.connection.off('post', this.onPost); + this.connection.off('note', this.onNote); this.connection.close(); document.removeEventListener('visibilitychange', this.onVisibilitychange); }); - this.onPost = post => { - this.posts.unshift(post); + this.onNote = note => { + this.notes.unshift(note); this.update(); - if (document.hidden && this.$root.$data.os.isSignedIn && post.userId !== this.$root.$data.os.i.id) { + if (document.hidden && this.$root.$data.os.isSignedIn && note.userId !== this.$root.$data.os.i.id) { this.unreadCount++; document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`; } @@ -162,19 +162,19 @@ </script> </mk-channel> -<mk-channel-post> +<mk-channel-note> <header> - <a class="index" @click="reply">{ post.index }:</a> - <a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(post.user) }</b></a> - <mk-time time={ post.createdAt }/> - <mk-time time={ post.createdAt } mode="detail"/> + <a class="index" @click="reply">{ note.index }:</a> + <a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a> + <mk-time time={ note.createdAt }/> + <mk-time time={ note.createdAt } mode="detail"/> <span>ID:<i>{ acct }</i></span> </header> <div> - <a v-if="post.reply">>>{ post.reply.index }</a> - { post.text } - <div class="media" v-if="post.media"> - <template each={ file in post.media }> + <a v-if="note.reply">>>{ note.reply.index }</a> + { note.text } + <div class="media" v-if="note.media"> + <template each={ file in note.media }> <a href={ file.url } target="_blank"> <img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/> </a> @@ -232,18 +232,18 @@ import getAcct from '../../../../acct/render'; import getUserName from '../../../../renderers/get-user-name'; - this.post = this.opts.post; + this.note = this.opts.note; this.form = this.opts.form; - this.acct = getAcct(this.post.user); - this.name = getUserName(this.post.user); + this.acct = getAcct(this.note.user); + this.name = getUserName(this.note.user); this.reply = () => { this.form.update({ - reply: this.post + reply: this.note }); }; </script> -</mk-channel-post> +</mk-channel-note> <mk-channel-form> <p v-if="reply"><b>>>{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p> @@ -251,8 +251,8 @@ <div class="actions"> <button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button> <button @click="drive">%fa:cloud%%i18n:ch.tags.mk-channel-form.drive%</button> - <button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="post"> - <template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:ch.tags.mk-channel-form.posting%' : '%i18n:ch.tags.mk-channel-form.post%' }<mk-ellipsis v-if="wait"/> + <button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="note"> + <template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:ch.tags.mk-channel-form.posting%' : '%i18n:ch.tags.mk-channel-form.note%' }<mk-ellipsis v-if="wait"/> </button> </div> <mk-uploader ref="uploader"/> @@ -321,7 +321,7 @@ this.$refs.text.value = ''; }; - this.post = () => { + this.note = () => { this.update({ wait: true }); @@ -330,7 +330,7 @@ ? this.files.map(f => f.id) : undefined; - this.$root.$data.os.api('posts/create', { + this.$root.$data.os.api('notes/create', { text: this.$refs.text.value == '' ? undefined : this.$refs.text.value, mediaIds: files, replyId: this.reply ? this.reply.id : undefined, diff --git a/src/client/app/common/mios.ts b/src/client/app/common/mios.ts index bcb8b60678..48e830810c 100644 --- a/src/client/app/common/mios.ts +++ b/src/client/app/common/mios.ts @@ -49,7 +49,7 @@ export type API = { post: (opts?: { reply?: any; - repost?: any; + renote?: any; }) => void; notify: (message: string) => void; @@ -312,7 +312,7 @@ export default class MiOS extends EventEmitter { // Finish init callback(); - //#region Post + //#region Note // Init service worker if (this.shouldRegisterSw) this.registerSw(); diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts index e99d502960..c19b1c5ad0 100644 --- a/src/client/app/common/scripts/compose-notification.ts +++ b/src/client/app/common/scripts/compose-notification.ts @@ -1,4 +1,4 @@ -import getPostSummary from '../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../renderers/get-note-summary'; import getReactionEmoji from '../../../../renderers/get-reaction-emoji'; import getUserName from '../../../../renderers/get-user-name'; @@ -23,28 +23,28 @@ export default function(type, data): Notification { case 'mention': return { title: `${getUserName(data.user)}さんから:`, - body: getPostSummary(data), + body: getNoteSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'reply': return { title: `${getUserName(data.user)}さんから返信:`, - body: getPostSummary(data), + body: getNoteSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'quote': return { title: `${getUserName(data.user)}さんが引用:`, - body: getPostSummary(data), + body: getNoteSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'reaction': return { title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, - body: getPostSummary(data.post), + body: getNoteSummary(data.note), icon: data.user.avatarUrl + '?thumbnail&size=64' }; diff --git a/src/client/app/common/scripts/parse-search-query.ts b/src/client/app/common/scripts/parse-search-query.ts index 4f09d2b93f..5f6ae3320a 100644 --- a/src/client/app/common/scripts/parse-search-query.ts +++ b/src/client/app/common/scripts/parse-search-query.ts @@ -19,8 +19,8 @@ export default function(qs: string) { case 'reply': q['reply'] = value == 'null' ? null : value == 'true'; break; - case 'repost': - q['repost'] = value == 'null' ? null : value == 'true'; + case 'renote': + q['renote'] = value == 'null' ? null : value == 'true'; break; case 'media': q['media'] = value == 'null' ? null : value == 'true'; diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index b58ba37ecb..6bfe43a800 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -4,7 +4,7 @@ import signin from './signin.vue'; import signup from './signup.vue'; import forkit from './forkit.vue'; import nav from './nav.vue'; -import postHtml from './post-html'; +import noteHtml from './note-html'; import poll from './poll.vue'; import pollEditor from './poll-editor.vue'; import reactionIcon from './reaction-icon.vue'; @@ -29,7 +29,7 @@ Vue.component('mk-signin', signin); Vue.component('mk-signup', signup); Vue.component('mk-forkit', forkit); Vue.component('mk-nav', nav); -Vue.component('mk-post-html', postHtml); +Vue.component('mk-note-html', noteHtml); Vue.component('mk-poll', poll); Vue.component('mk-poll-editor', pollEditor); Vue.component('mk-reaction-icon', reactionIcon); diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue index 1518602c63..7200b59bb2 100644 --- a/src/client/app/common/views/components/messaging-room.message.vue +++ b/src/client/app/common/views/components/messaging-room.message.vue @@ -10,7 +10,7 @@ <img src="/assets/desktop/messaging/delete.png" alt="Delete"/> </button> <div class="content" v-if="!message.isDeleted"> - <mk-post-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/> + <mk-note-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/> <div class="file" v-if="message.file"> <a :href="message.file.url" target="_blank" :title="message.file.name"> <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> diff --git a/src/client/app/common/views/components/post-html.ts b/src/client/app/common/views/components/note-html.ts index 8d85316523..24e750a671 100644 --- a/src/client/app/common/views/components/post-html.ts +++ b/src/client/app/common/views/components/note-html.ts @@ -9,7 +9,7 @@ const flatten = list => list.reduce( (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] ); -export default Vue.component('mk-post-html', { +export default Vue.component('mk-note-html', { props: { text: { type: String, diff --git a/src/client/app/common/views/components/post-menu.vue b/src/client/app/common/views/components/note-menu.vue index 35116db7e2..d053748728 100644 --- a/src/client/app/common/views/components/post-menu.vue +++ b/src/client/app/common/views/components/note-menu.vue @@ -1,8 +1,8 @@ <template> -<div class="mk-post-menu"> +<div class="mk-note-menu"> <div class="backdrop" ref="backdrop" @click="close"></div> <div class="popover" :class="{ compact }" ref="popover"> - <button v-if="post.userId == os.i.id" @click="pin">%i18n:common.tags.mk-post-menu.pin%</button> + <button v-if="note.userId == os.i.id" @click="pin">%i18n:common.tags.mk-note-menu.pin%</button> </div> </div> </template> @@ -12,7 +12,7 @@ import Vue from 'vue'; import * as anime from 'animejs'; export default Vue.extend({ - props: ['post', 'source', 'compact'], + props: ['note', 'source', 'compact'], mounted() { this.$nextTick(() => { const popover = this.$refs.popover as any; @@ -51,7 +51,7 @@ export default Vue.extend({ methods: { pin() { (this as any).api('i/pin', { - postId: this.post.id + noteId: this.note.id }).then(() => { this.$destroy(); }); @@ -83,7 +83,7 @@ export default Vue.extend({ <style lang="stylus" scoped> $border-color = rgba(27, 31, 35, 0.15) -.mk-post-menu +.mk-note-menu position initial > .backdrop diff --git a/src/client/app/common/views/components/poll.vue b/src/client/app/common/views/components/poll.vue index 711d89720e..eb29aa8837 100644 --- a/src/client/app/common/views/components/poll.vue +++ b/src/client/app/common/views/components/poll.vue @@ -22,7 +22,7 @@ <script lang="ts"> import Vue from 'vue'; export default Vue.extend({ - props: ['post'], + props: ['note'], data() { return { showResult: false @@ -30,7 +30,7 @@ export default Vue.extend({ }, computed: { poll(): any { - return this.post.poll; + return this.note.poll; }, total(): number { return this.poll.choices.reduce((a, b) => a + b.votes, 0); @@ -48,8 +48,8 @@ export default Vue.extend({ }, vote(id) { if (this.poll.choices.some(c => c.isVoted)) return; - (this as any).api('posts/polls/vote', { - postId: this.post.id, + (this as any).api('notes/polls/vote', { + noteId: this.note.id, choice: id }).then(() => { this.poll.choices.forEach(c => { diff --git a/src/client/app/common/views/components/reaction-picker.vue b/src/client/app/common/views/components/reaction-picker.vue index bcb6b2b965..fa1998dca9 100644 --- a/src/client/app/common/views/components/reaction-picker.vue +++ b/src/client/app/common/views/components/reaction-picker.vue @@ -25,7 +25,7 @@ import * as anime from 'animejs'; const placeholder = '%i18n:common.tags.mk-reaction-picker.choose-reaction%'; export default Vue.extend({ - props: ['post', 'source', 'compact', 'cb'], + props: ['note', 'source', 'compact', 'cb'], data() { return { title: placeholder @@ -68,8 +68,8 @@ export default Vue.extend({ }, methods: { react(reaction) { - (this as any).api('posts/reactions/create', { - postId: this.post.id, + (this as any).api('notes/reactions/create', { + noteId: this.note.id, reaction: reaction }).then(() => { if (this.cb) this.cb(); diff --git a/src/client/app/common/views/components/reactions-viewer.vue b/src/client/app/common/views/components/reactions-viewer.vue index 246451008f..1afcf525d2 100644 --- a/src/client/app/common/views/components/reactions-viewer.vue +++ b/src/client/app/common/views/components/reactions-viewer.vue @@ -17,10 +17,10 @@ <script lang="ts"> import Vue from 'vue'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { reactions(): number { - return this.post.reactionCounts; + return this.note.reactionCounts; } } }); diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index bfb4b2bbe0..61616da14c 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -1,21 +1,21 @@ <template> <div class="mk-welcome-timeline"> - <div v-for="post in posts"> - <router-link class="avatar-anchor" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> + <div v-for="note in notes"> + <router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> </router-link> <div class="body"> <header> - <router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ getUserName(post.user) }}</router-link> - <span class="username">@{{ getAcct(post.user) }}</span> + <router-link class="name" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id">{{ getUserName(note.user) }}</router-link> + <span class="username">@{{ getAcct(note.user) }}</span> <div class="info"> - <router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="created-at" :to="`/@${getAcct(note.user)}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </div> </header> <div class="text"> - <mk-post-html :text="post.text"/> + <mk-note-html :text="note.text"/> </div> </div> </div> @@ -31,7 +31,7 @@ export default Vue.extend({ data() { return { fetching: true, - posts: [] + notes: [] }; }, mounted() { @@ -42,14 +42,14 @@ export default Vue.extend({ getUserName, fetch(cb?) { this.fetching = true; - (this as any).api('posts', { + (this as any).api('notes', { reply: false, - repost: false, + renote: false, media: false, poll: false, bot: false - }).then(posts => { - this.posts = posts; + }).then(notes => { + this.notes = notes; this.fetching = false; }); } diff --git a/src/client/app/desktop/api/post.ts b/src/client/app/desktop/api/post.ts index cf49615df3..b569610e1d 100644 --- a/src/client/app/desktop/api/post.ts +++ b/src/client/app/desktop/api/post.ts @@ -1,12 +1,12 @@ import PostFormWindow from '../views/components/post-form-window.vue'; -import RepostFormWindow from '../views/components/repost-form-window.vue'; +import RenoteFormWindow from '../views/components/renote-form-window.vue'; export default function(opts) { const o = opts || {}; - if (o.repost) { - const vm = new RepostFormWindow({ + if (o.renote) { + const vm = new RenoteFormWindow({ propsData: { - repost: o.repost + renote: o.renote } }).$mount(); document.body.appendChild(vm.$el); diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index b95e168544..f57d42aa6f 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -28,7 +28,7 @@ import MkSelectDrive from './views/pages/selectdrive.vue'; import MkDrive from './views/pages/drive.vue'; import MkHomeCustomize from './views/pages/home-customize.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue'; -import MkPost from './views/pages/post.vue'; +import MkNote from './views/pages/note.vue'; import MkSearch from './views/pages/search.vue'; import MkOthello from './views/pages/othello.vue'; @@ -57,7 +57,7 @@ init(async (launch) => { { path: '/othello', component: MkOthello }, { path: '/othello/:game', component: MkOthello }, { path: '/@:user', component: MkUser }, - { path: '/@:user/:post', component: MkPost } + { path: '/@:user/:note', component: MkNote } ] }); @@ -114,8 +114,8 @@ function registerNotifications(stream: HomeStreamManager) { setTimeout(n.close.bind(n), 5000); }); - connection.on('mention', post => { - const _n = composeNotification('mention', post); + connection.on('mention', note => { + const _n = composeNotification('mention', note); const n = new Notification(_n.title, { body: _n.body, icon: _n.icon @@ -123,8 +123,8 @@ function registerNotifications(stream: HomeStreamManager) { setTimeout(n.close.bind(n), 6000); }); - connection.on('reply', post => { - const _n = composeNotification('reply', post); + connection.on('reply', note => { + const _n = composeNotification('reply', note); const n = new Notification(_n.title, { body: _n.body, icon: _n.icon @@ -132,8 +132,8 @@ function registerNotifications(stream: HomeStreamManager) { setTimeout(n.close.bind(n), 6000); }); - connection.on('quote', post => { - const _n = composeNotification('quote', post); + connection.on('quote', note => { + const _n = composeNotification('quote', note); const n = new Notification(_n.title, { body: _n.body, icon: _n.icon diff --git a/src/client/app/desktop/views/components/activity.calendar.vue b/src/client/app/desktop/views/components/activity.calendar.vue index 72233e9aca..8b43536c2b 100644 --- a/src/client/app/desktop/views/components/activity.calendar.vue +++ b/src/client/app/desktop/views/components/activity.calendar.vue @@ -29,7 +29,7 @@ import Vue from 'vue'; export default Vue.extend({ props: ['data'], created() { - this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); + this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); const peak = Math.max.apply(null, this.data.map(d => d.total)); let x = 0; diff --git a/src/client/app/desktop/views/components/activity.chart.vue b/src/client/app/desktop/views/components/activity.chart.vue index 5057786ed4..175c5d37ed 100644 --- a/src/client/app/desktop/views/components/activity.chart.vue +++ b/src/client/app/desktop/views/components/activity.chart.vue @@ -1,8 +1,8 @@ <template> <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown"> - <title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title> + <title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title> <polyline - :points="pointsPost" + :points="pointsNote" fill="none" stroke-width="1" stroke="#41ddde"/> @@ -12,7 +12,7 @@ stroke-width="1" stroke="#f7796c"/> <polyline - :points="pointsRepost" + :points="pointsRenote" fill="none" stroke-width="1" stroke="#a1de41"/> @@ -48,24 +48,24 @@ export default Vue.extend({ viewBoxY: 60, zoom: 1, pos: 0, - pointsPost: null, + pointsNote: null, pointsReply: null, - pointsRepost: null, + pointsRenote: null, pointsTotal: null }; }, created() { this.data.reverse(); - this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); + this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); this.render(); }, methods: { render() { const peak = Math.max.apply(null, this.data.map(d => d.total)); if (peak != 0) { - this.pointsPost = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '); + this.pointsNote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); this.pointsReply = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); - this.pointsRepost = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '); + this.pointsRenote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); this.pointsTotal = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); } }, diff --git a/src/client/app/desktop/views/components/index.ts b/src/client/app/desktop/views/components/index.ts index 3798bf6d2d..4f61f43692 100644 --- a/src/client/app/desktop/views/components/index.ts +++ b/src/client/app/desktop/views/components/index.ts @@ -4,23 +4,23 @@ import ui from './ui.vue'; import uiNotification from './ui-notification.vue'; import home from './home.vue'; import timeline from './timeline.vue'; -import posts from './posts.vue'; -import subPostContent from './sub-post-content.vue'; +import notes from './notes.vue'; +import subNoteContent from './sub-note-content.vue'; import window from './window.vue'; -import postFormWindow from './post-form-window.vue'; -import repostFormWindow from './repost-form-window.vue'; +import noteFormWindow from './post-form-window.vue'; +import renoteFormWindow from './renote-form-window.vue'; import analogClock from './analog-clock.vue'; import ellipsisIcon from './ellipsis-icon.vue'; import mediaImage from './media-image.vue'; import mediaImageDialog from './media-image-dialog.vue'; import mediaVideo from './media-video.vue'; import notifications from './notifications.vue'; -import postForm from './post-form.vue'; -import repostForm from './repost-form.vue'; +import noteForm from './post-form.vue'; +import renoteForm from './renote-form.vue'; import followButton from './follow-button.vue'; -import postPreview from './post-preview.vue'; +import notePreview from './note-preview.vue'; import drive from './drive.vue'; -import postDetail from './post-detail.vue'; +import noteDetail from './note-detail.vue'; import settings from './settings.vue'; import calendar from './calendar.vue'; import activity from './activity.vue'; @@ -34,23 +34,23 @@ Vue.component('mk-ui', ui); Vue.component('mk-ui-notification', uiNotification); Vue.component('mk-home', home); Vue.component('mk-timeline', timeline); -Vue.component('mk-posts', posts); -Vue.component('mk-sub-post-content', subPostContent); +Vue.component('mk-notes', notes); +Vue.component('mk-sub-note-content', subNoteContent); Vue.component('mk-window', window); -Vue.component('mk-post-form-window', postFormWindow); -Vue.component('mk-repost-form-window', repostFormWindow); +Vue.component('mk-post-form-window', noteFormWindow); +Vue.component('mk-renote-form-window', renoteFormWindow); Vue.component('mk-analog-clock', analogClock); Vue.component('mk-ellipsis-icon', ellipsisIcon); Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-image-dialog', mediaImageDialog); Vue.component('mk-media-video', mediaVideo); Vue.component('mk-notifications', notifications); -Vue.component('mk-post-form', postForm); -Vue.component('mk-repost-form', repostForm); +Vue.component('mk-post-form', noteForm); +Vue.component('mk-renote-form', renoteForm); Vue.component('mk-follow-button', followButton); -Vue.component('mk-post-preview', postPreview); +Vue.component('mk-note-preview', notePreview); Vue.component('mk-drive', drive); -Vue.component('mk-post-detail', postDetail); +Vue.component('mk-note-detail', noteDetail); Vue.component('mk-settings', settings); Vue.component('mk-calendar', calendar); Vue.component('mk-activity', activity); diff --git a/src/client/app/desktop/views/components/mentions.vue b/src/client/app/desktop/views/components/mentions.vue index 90a92495b7..fc3a7af75d 100644 --- a/src/client/app/desktop/views/components/mentions.vue +++ b/src/client/app/desktop/views/components/mentions.vue @@ -7,12 +7,12 @@ <div class="fetching" v-if="fetching"> <mk-ellipsis-icon/> </div> - <p class="empty" v-if="posts.length == 0 && !fetching"> + <p class="empty" v-if="notes.length == 0 && !fetching"> %fa:R comments% <span v-if="mode == 'all'">あなた宛ての投稿はありません。</span> <span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません。</span> </p> - <mk-posts :posts="posts" ref="timeline"/> + <mk-notes :notes="notes" ref="timeline"/> </div> </template> @@ -24,7 +24,7 @@ export default Vue.extend({ fetching: true, moreFetching: false, mode: 'all', - posts: [] + notes: [] }; }, watch: { @@ -56,23 +56,23 @@ export default Vue.extend({ }, fetch(cb?) { this.fetching = true; - this.posts = []; - (this as any).api('posts/mentions', { + this.notes = []; + (this as any).api('notes/mentions', { following: this.mode == 'following' - }).then(posts => { - this.posts = posts; + }).then(notes => { + this.notes = notes; this.fetching = false; if (cb) cb(); }); }, more() { - if (this.moreFetching || this.fetching || this.posts.length == 0) return; + if (this.moreFetching || this.fetching || this.notes.length == 0) return; this.moreFetching = true; - (this as any).api('posts/mentions', { + (this as any).api('notes/mentions', { following: this.mode == 'following', - untilId: this.posts[this.posts.length - 1].id - }).then(posts => { - this.posts = this.posts.concat(posts); + untilId: this.notes[this.notes.length - 1].id + }).then(notes => { + this.notes = this.notes.concat(notes); this.moreFetching = false; }); } diff --git a/src/client/app/desktop/views/components/post-detail.sub.vue b/src/client/app/desktop/views/components/note-detail.sub.vue index 496003eb8b..0159d1ad47 100644 --- a/src/client/app/desktop/views/components/post-detail.sub.vue +++ b/src/client/app/desktop/views/components/note-detail.sub.vue @@ -1,24 +1,24 @@ <template> <div class="sub" :title="title"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> </router-link> <div class="main"> <header> <div class="left"> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</router-link> <span class="username">@{{ acct }}</span> </div> <div class="right"> - <router-link class="time" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="time" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </div> </header> <div class="body"> - <mk-post-html v-if="post.text" :text="post.text" :i="os.i" :class="$style.text"/> - <div class="media" v-if="post.media > 0"> - <mk-media-list :media-list="post.media"/> + <mk-note-html v-if="note.text" :text="note.text" :i="os.i" :class="$style.text"/> + <div class="media" v-if="note.media > 0"> + <mk-media-list :media-list="note.media"/> </div> </div> </div> @@ -32,16 +32,16 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); }, title(): string { - return dateStringify(this.post.createdAt); + return dateStringify(this.note.createdAt); } } }); diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue new file mode 100644 index 0000000000..790f797ad6 --- /dev/null +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -0,0 +1,448 @@ +<template> +<div class="mk-note-detail" :title="title"> + <button + class="read-more" + v-if="p.reply && p.reply.replyId && context == null" + title="会話をもっと読み込む" + @click="fetchContext" + :disabled="contextFetching" + > + <template v-if="!contextFetching">%fa:ellipsis-v%</template> + <template v-if="contextFetching">%fa:spinner .pulse%</template> + </button> + <div class="context"> + <x-sub v-for="note in context" :key="note.id" :note="note"/> + </div> + <div class="reply-to" v-if="p.reply"> + <x-sub :note="p.reply"/> + </div> + <div class="renote" v-if="isRenote"> + <p> + <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + </router-link> + %fa:retweet% + <router-link class="name" :href="`/@${acct}`">{{ getUserName(note.user) }}</router-link> + がRenote + </p> + </div> + <article> + <router-link class="avatar-anchor" :to="`/@${pAcct}`"> + <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> + </router-link> + <header> + <router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ getUserName(p.user) }}</router-link> + <span class="username">@{{ pAcct }}</span> + <router-link class="time" :to="`/@${pAcct}/${p.id}`"> + <mk-time :time="p.createdAt"/> + </router-link> + </header> + <div class="body"> + <mk-note-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> + <div class="media" v-if="p.media.length > 0"> + <mk-media-list :media-list="p.media"/> + </div> + <mk-poll v-if="p.poll" :note="p"/> + <mk-url-preview v-for="url in urls" :url="url" :key="url"/> + <div class="tags" v-if="p.tags && p.tags.length > 0"> + <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> + </div> + <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> + <div class="map" v-if="p.geo" ref="map"></div> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> + </div> + </div> + <footer> + <mk-reactions-viewer :note="p"/> + <button @click="reply" title="返信"> + %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> + </button> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> + </button> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="リアクション"> + %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> + </button> + <button @click="menu" ref="menuButton"> + %fa:ellipsis-h% + </button> + </footer> + </article> + <div class="replies" v-if="!compact"> + <x-sub v-for="note in replies" :key="note.id" :note="note"/> + </div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import dateStringify from '../../../common/scripts/date-stringify'; +import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; +import parse from '../../../../../text/parse'; + +import MkPostFormWindow from './post-form-window.vue'; +import MkRenoteFormWindow from './renote-form-window.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; +import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; +import XSub from './note-detail.sub.vue'; + +export default Vue.extend({ + components: { + XSub + }, + + props: { + note: { + type: Object, + required: true + }, + compact: { + default: false + } + }, + + data() { + return { + context: [], + contextFetching: false, + replies: [] + }; + }, + + computed: { + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); + }, + p(): any { + return this.isRenote ? this.note.renote : this.note; + }, + reactionsCount(): number { + return this.p.reactionCounts + ? Object.keys(this.p.reactionCounts) + .map(key => this.p.reactionCounts[key]) + .reduce((a, b) => a + b) + : 0; + }, + title(): string { + return dateStringify(this.p.createdAt); + }, + acct(): string { + return getAcct(this.note.user); + }, + name(): string { + return getUserName(this.note.user); + }, + pAcct(): string { + return getAcct(this.p.user); + }, + pName(): string { + return getUserName(this.p.user); + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } + } + }, + + mounted() { + // Get replies + if (!this.compact) { + (this as any).api('notes/replies', { + noteId: this.p.id, + limit: 8 + }).then(replies => { + this.replies = replies; + }); + } + + // Draw map + if (this.p.geo) { + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true; + if (shouldShowMap) { + (this as any).os.getGoogleMaps().then(maps => { + const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); + const map = new maps.Map(this.$refs.map, { + center: uluru, + zoom: 15 + }); + new maps.Marker({ + position: uluru, + map: map + }); + }); + } + } + }, + + methods: { + fetchContext() { + this.contextFetching = true; + + // Fetch context + (this as any).api('notes/context', { + noteId: this.p.replyId + }).then(context => { + this.contextFetching = false; + this.context = context.reverse(); + }); + }, + reply() { + (this as any).os.new(MkPostFormWindow, { + reply: this.p + }); + }, + renote() { + (this as any).os.new(MkRenoteFormWindow, { + note: this.p + }); + }, + react() { + (this as any).os.new(MkReactionPicker, { + source: this.$refs.reactButton, + note: this.p + }); + }, + menu() { + (this as any).os.new(MkNoteMenu, { + source: this.$refs.menuButton, + note: this.p + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.mk-note-detail + margin 0 + padding 0 + overflow hidden + text-align left + background #fff + border solid 1px rgba(0, 0, 0, 0.1) + border-radius 8px + + > .read-more + display block + margin 0 + padding 10px 0 + width 100% + font-size 1em + text-align center + color #999 + cursor pointer + background #fafafa + outline none + border none + border-bottom solid 1px #eef0f2 + border-radius 6px 6px 0 0 + + &:hover + background #f6f6f6 + + &:active + background #f0f0f0 + + &:disabled + color #ccc + + > .context + > * + border-bottom 1px solid #eef0f2 + + > .renote + color #9dbb00 + background linear-gradient(to bottom, #edfde2 0%, #fff 100%) + + > p + margin 0 + padding 16px 32px + + .avatar-anchor + display inline-block + + .avatar + vertical-align bottom + min-width 28px + min-height 28px + max-width 28px + max-height 28px + margin 0 8px 0 0 + border-radius 6px + + [data-fa] + margin-right 4px + + .name + font-weight bold + + & + article + padding-top 8px + + > .reply-to + border-bottom 1px solid #eef0f2 + + > article + padding 28px 32px 18px 32px + + &:after + content "" + display block + clear both + + &:hover + > .main > footer > button + color #888 + + > .avatar-anchor + display block + width 60px + height 60px + + > .avatar + display block + width 60px + height 60px + margin 0 + border-radius 8px + vertical-align bottom + + > header + position absolute + top 28px + left 108px + width calc(100% - 108px) + + > .name + display inline-block + margin 0 + line-height 24px + color #777 + font-size 18px + font-weight 700 + text-align left + text-decoration none + + &:hover + text-decoration underline + + > .username + display block + text-align left + margin 0 + color #ccc + + > .time + position absolute + top 0 + right 32px + font-size 1em + color #c0c0c0 + + > .body + padding 8px 0 + + > .renote + margin 8px 0 + + > .mk-note-preview + padding 16px + border dashed 1px #c0dac6 + border-radius 8px + + > .location + margin 4px 0 + font-size 12px + color #ccc + + > .map + width 100% + height 300px + + &:empty + display none + + > .mk-url-preview + margin-top 8px + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + &:hover + text-decoration none + background #e2e7ec + + > footer + font-size 1.2em + + > button + margin 0 28px 0 0 + padding 8px + background transparent + border none + font-size 1em + color #ddd + cursor pointer + + &:hover + color #666 + + > .count + display inline + margin 0 0 0 8px + color #999 + + &.reacted + color $theme-color + + > .replies + > * + border-top 1px solid #eef0f2 + +</style> + +<style lang="stylus" module> +.text + cursor default + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 1.5em + color #717171 +</style> diff --git a/src/client/app/desktop/views/components/post-preview.vue b/src/client/app/desktop/views/components/note-preview.vue index 99d9442d93..bff199c09d 100644 --- a/src/client/app/desktop/views/components/post-preview.vue +++ b/src/client/app/desktop/views/components/note-preview.vue @@ -1,18 +1,18 @@ <template> -<div class="mk-post-preview" :title="title"> +<div class="mk-note-preview" :title="title"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> <span class="username">@{{ acct }}</span> - <router-link class="time" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="time" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </header> <div class="body"> - <mk-sub-post-content class="text" :post="post"/> + <mk-sub-note-content class="text" :note="note"/> </div> </div> </div> @@ -25,23 +25,23 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); }, title(): string { - return dateStringify(this.post.createdAt); + return dateStringify(this.note.createdAt); } } }); </script> <style lang="stylus" scoped> -.mk-post-preview +.mk-note-preview font-size 0.9em background #fff diff --git a/src/client/app/desktop/views/components/posts.post.sub.vue b/src/client/app/desktop/views/components/notes.note.sub.vue index a9cd0a9279..b49d12b92e 100644 --- a/src/client/app/desktop/views/components/posts.post.sub.vue +++ b/src/client/app/desktop/views/components/notes.note.sub.vue @@ -1,18 +1,18 @@ <template> <div class="sub" :title="title"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> <span class="username">@{{ acct }}</span> - <router-link class="created-at" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="created-at" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </header> <div class="body"> - <mk-sub-post-content class="text" :post="post"/> + <mk-sub-note-content class="text" :note="note"/> </div> </div> </div> @@ -25,16 +25,16 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name(): string { - return getUserName(this.post.user); + return getUserName(this.note.user); }, title(): string { - return dateStringify(this.post.createdAt); + return dateStringify(this.note.createdAt); } } }); diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue new file mode 100644 index 0000000000..924642c01a --- /dev/null +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -0,0 +1,596 @@ +<template> +<div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> + <div class="reply-to" v-if="p.reply"> + <x-sub :note="p.reply"/> + </div> + <div class="renote" v-if="isRenote"> + <p> + <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + </router-link> + %fa:retweet% + <span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> + <a class="name" :href="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a> + <span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> + </p> + <mk-time :time="note.createdAt"/> + </div> + <article> + <router-link class="avatar-anchor" :to="`/@${acct}`"> + <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> + </router-link> + <div class="main"> + <header> + <router-link class="name" :to="`/@${acct}`" v-user-preview="p.user.id">{{ acct }}</router-link> + <span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span> + <span class="username">@{{ acct }}</span> + <div class="info"> + <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> + <span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> + <router-link class="created-at" :to="url"> + <mk-time :time="p.createdAt"/> + </router-link> + </div> + </header> + <div class="body"> + <p class="channel" v-if="p.channel"> + <a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>: + </p> + <div class="text"> + <a class="reply" v-if="p.reply">%fa:reply%</a> + <mk-note-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> + <a class="rp" v-if="p.renote">RP:</a> + </div> + <div class="media" v-if="p.media.length > 0"> + <mk-media-list :media-list="p.media"/> + </div> + <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> + <div class="tags" v-if="p.tags && p.tags.length > 0"> + <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> + </div> + <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> + <div class="map" v-if="p.geo" ref="map"></div> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> + </div> + <mk-url-preview v-for="url in urls" :url="url" :key="url"/> + </div> + <footer> + <mk-reactions-viewer :note="p" ref="reactionsViewer"/> + <button @click="reply" title="%i18n:desktop.tags.mk-timeline-note.reply%"> + %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> + </button> + <button @click="renote" title="%i18n:desktop.tags.mk-timeline-note.renote%"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> + </button> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-note.add-reaction%"> + %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> + </button> + <button @click="menu" ref="menuButton"> + %fa:ellipsis-h% + </button> + <button title="%i18n:desktop.tags.mk-timeline-note.detail"> + <template v-if="!isDetailOpened">%fa:caret-down%</template> + <template v-if="isDetailOpened">%fa:caret-up%</template> + </button> + </footer> + </div> + </article> + <div class="detail" v-if="isDetailOpened"> + <mk-note-status-graph width="462" height="130" :note="p"/> + </div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import dateStringify from '../../../common/scripts/date-stringify'; +import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; +import parse from '../../../../../text/parse'; + +import MkPostFormWindow from './post-form-window.vue'; +import MkRenoteFormWindow from './renote-form-window.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; +import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; +import XSub from './notes.note.sub.vue'; + +function focus(el, fn) { + const target = fn(el); + if (target) { + if (target.hasAttribute('tabindex')) { + target.focus(); + } else { + focus(target, fn); + } + } +} + +export default Vue.extend({ + components: { + XSub + }, + + props: ['note'], + + data() { + return { + isDetailOpened: false, + connection: null, + connectionId: null + }; + }, + + computed: { + acct(): string { + return getAcct(this.p.user); + }, + name(): string { + return getUserName(this.p.user); + }, + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); + }, + p(): any { + return this.isRenote ? this.note.renote : this.note; + }, + reactionsCount(): number { + return this.p.reactionCounts + ? Object.keys(this.p.reactionCounts) + .map(key => this.p.reactionCounts[key]) + .reduce((a, b) => a + b) + : 0; + }, + title(): string { + return dateStringify(this.p.createdAt); + }, + url(): string { + return `/@${this.acct}/${this.p.id}`; + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } + } + }, + + created() { + if ((this as any).os.isSignedIn) { + this.connection = (this as any).os.stream.getConnection(); + this.connectionId = (this as any).os.stream.use(); + } + }, + + mounted() { + this.capture(true); + + if ((this as any).os.isSignedIn) { + this.connection.on('_connected_', this.onStreamConnected); + } + + // Draw map + if (this.p.geo) { + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true; + if (shouldShowMap) { + (this as any).os.getGoogleMaps().then(maps => { + const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); + const map = new maps.Map(this.$refs.map, { + center: uluru, + zoom: 15 + }); + new maps.Marker({ + position: uluru, + map: map + }); + }); + } + } + }, + + beforeDestroy() { + this.decapture(true); + + if ((this as any).os.isSignedIn) { + this.connection.off('_connected_', this.onStreamConnected); + (this as any).os.stream.dispose(this.connectionId); + } + }, + + methods: { + capture(withHandler = false) { + if ((this as any).os.isSignedIn) { + this.connection.send({ + type: 'capture', + id: this.p.id + }); + if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); + } + }, + decapture(withHandler = false) { + if ((this as any).os.isSignedIn) { + this.connection.send({ + type: 'decapture', + id: this.p.id + }); + if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); + } + }, + onStreamConnected() { + this.capture(); + }, + onStreamNoteUpdated(data) { + const note = data.note; + if (note.id == this.note.id) { + this.$emit('update:note', note); + } else if (note.id == this.note.renoteId) { + this.note.renote = note; + } + }, + reply() { + (this as any).os.new(MkPostFormWindow, { + reply: this.p + }); + }, + renote() { + (this as any).os.new(MkRenoteFormWindow, { + note: this.p + }); + }, + react() { + (this as any).os.new(MkReactionPicker, { + source: this.$refs.reactButton, + note: this.p + }); + }, + menu() { + (this as any).os.new(MkNoteMenu, { + source: this.$refs.menuButton, + note: this.p + }); + }, + onKeydown(e) { + let shouldBeCancel = true; + + switch (true) { + case e.which == 38: // [↑] + case e.which == 74: // [j] + case e.which == 9 && e.shiftKey: // [Shift] + [Tab] + focus(this.$el, e => e.previousElementSibling); + break; + + case e.which == 40: // [↓] + case e.which == 75: // [k] + case e.which == 9: // [Tab] + focus(this.$el, e => e.nextElementSibling); + break; + + case e.which == 81: // [q] + case e.which == 69: // [e] + this.renote(); + break; + + case e.which == 70: // [f] + case e.which == 76: // [l] + //this.like(); + break; + + case e.which == 82: // [r] + this.reply(); + break; + + default: + shouldBeCancel = false; + } + + if (shouldBeCancel) e.preventDefault(); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.note + margin 0 + padding 0 + background #fff + border-bottom solid 1px #eaeaea + + &:first-child + border-top-left-radius 6px + border-top-right-radius 6px + + > .renote + border-top-left-radius 6px + border-top-right-radius 6px + + &:last-of-type + border-bottom none + + &:focus + z-index 1 + + &:after + content "" + pointer-events none + position absolute + top 2px + right 2px + bottom 2px + left 2px + border 2px solid rgba($theme-color, 0.3) + border-radius 4px + + > .renote + color #9dbb00 + background linear-gradient(to bottom, #edfde2 0%, #fff 100%) + + > p + margin 0 + padding 16px 32px + line-height 28px + + .avatar-anchor + display inline-block + + .avatar + vertical-align bottom + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px + + [data-fa] + margin-right 4px + + .name + font-weight bold + + > .mk-time + position absolute + top 16px + right 32px + font-size 0.9em + line-height 28px + + & + article + padding-top 8px + + > .reply-to + padding 0 16px + background rgba(0, 0, 0, 0.0125) + + > .mk-note-preview + background transparent + + > article + padding 28px 32px 18px 32px + + &:after + content "" + display block + clear both + + &:hover + > .main > footer > button + color #888 + + > .avatar-anchor + display block + float left + margin 0 16px 10px 0 + //position -webkit-sticky + //position sticky + //top 74px + + > .avatar + display block + width 58px + height 58px + margin 0 + border-radius 8px + vertical-align bottom + + > .main + float left + width calc(100% - 74px) + + > header + display flex + align-items center + margin-bottom 4px + white-space nowrap + + > .name + display block + margin 0 .5em 0 0 + padding 0 + overflow hidden + color #627079 + font-size 1em + font-weight bold + text-decoration none + text-overflow ellipsis + + &:hover + text-decoration underline + + > .is-bot + margin 0 .5em 0 0 + padding 1px 6px + font-size 12px + color #aaa + border solid 1px #ddd + border-radius 3px + + > .username + margin 0 .5em 0 0 + color #ccc + + > .info + margin-left auto + font-size 0.9em + + > .mobile + margin-right 8px + color #ccc + + > .app + margin-right 8px + padding-right 8px + color #ccc + border-right solid 1px #eaeaea + + > .created-at + color #c0c0c0 + + > .body + + > .text + cursor default + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 1.1em + color #717171 + + >>> .quote + margin 8px + padding 6px 12px + color #aaa + border-left solid 3px #eee + + > .reply + margin-right 8px + color #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + > .location + margin 4px 0 + font-size 12px + color #ccc + + > .map + width 100% + height 300px + + &:empty + display none + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + &:hover + text-decoration none + background #e2e7ec + + .mk-url-preview + margin-top 8px + + > .channel + margin 0 + + > .mk-poll + font-size 80% + + > .renote + margin 8px 0 + + > .mk-note-preview + padding 16px + border dashed 1px #c0dac6 + border-radius 8px + + > footer + > button + margin 0 28px 0 0 + padding 0 8px + line-height 32px + font-size 1em + color #ddd + background transparent + border none + cursor pointer + + &:hover + color #666 + + > .count + display inline + margin 0 0 0 8px + color #999 + + &.reacted + color $theme-color + + &:last-child + position absolute + right 0 + margin 0 + + > .detail + padding-top 4px + background rgba(0, 0, 0, 0.0125) + +</style> + +<style lang="stylus" module> +.text + + code + padding 4px 8px + margin 0 0.5em + font-size 80% + color #525252 + background #f8f8f8 + border-radius 2px + + pre > code + padding 16px + margin 0 + + [data-is-me]:after + content "you" + padding 0 4px + margin-left 4px + font-size 80% + color $theme-color-foreground + background $theme-color + border-radius 4px +</style> diff --git a/src/client/app/desktop/views/components/posts.vue b/src/client/app/desktop/views/components/notes.vue index 5031667c7c..b5f6957a16 100644 --- a/src/client/app/desktop/views/components/posts.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -1,10 +1,10 @@ <template> -<div class="mk-posts"> - <template v-for="(post, i) in _posts"> - <x-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> - <p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> - <span>%fa:angle-up%{{ post._datetext }}</span> - <span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> +<div class="mk-notes"> + <template v-for="(note, i) in _notes"> + <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> + <p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> + <span>%fa:angle-up%{{ note._datetext }}</span> + <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> </p> </template> <footer> @@ -15,26 +15,26 @@ <script lang="ts"> import Vue from 'vue'; -import XPost from './posts.post.vue'; +import XNote from './notes.note.vue'; export default Vue.extend({ components: { - XPost + XNote }, props: { - posts: { + notes: { type: Array, default: () => [] } }, computed: { - _posts(): any[] { - return (this.posts as any).map(post => { - const date = new Date(post.createdAt).getDate(); - const month = new Date(post.createdAt).getMonth() + 1; - post._date = date; - post._datetext = `${month}月 ${date}日`; - return post; + _notes(): any[] { + return (this.notes as any).map(note => { + const date = new Date(note.createdAt).getDate(); + const month = new Date(note.createdAt).getMonth() + 1; + note._date = date; + note._datetext = `${month}月 ${date}日`; + return note; }); } }, @@ -42,15 +42,15 @@ export default Vue.extend({ focus() { (this.$el as any).children[0].focus(); }, - onPostUpdated(i, post) { - Vue.set((this as any).posts, i, post); + onNoteUpdated(i, note) { + Vue.set((this as any).notes, i, note); } } }); </script> <style lang="stylus" scoped> -.mk-posts +.mk-notes > .date display block diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue index d8b8eab212..100a803cc2 100644 --- a/src/client/app/desktop/views/components/notifications.vue +++ b/src/client/app/desktop/views/components/notifications.vue @@ -13,33 +13,33 @@ <mk-reaction-icon :reaction="notification.reaction"/> <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> </p> - <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% + <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% </router-link> </div> </template> - <template v-if="notification.type == 'repost'"> - <router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> + <template v-if="notification.type == 'renote'"> + <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> </router-link> <div class="text"> <p>%fa:retweet% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> + <router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> </p> - <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% + <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% </router-link> </div> </template> <template v-if="notification.type == 'quote'"> - <router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> + <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> </router-link> <div class="text"> <p>%fa:quote-left% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> + <router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> </p> - <router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> + <router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> </div> </template> <template v-if="notification.type == 'follow'"> @@ -53,25 +53,25 @@ </div> </template> <template v-if="notification.type == 'reply'"> - <router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> + <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> </router-link> <div class="text"> <p>%fa:reply% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> + <router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> </p> - <router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> + <router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> </div> </template> <template v-if="notification.type == 'mention'"> - <router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> + <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> </router-link> <div class="text"> <p>%fa:at% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> + <router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> </p> - <a class="post-preview" :href="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</a> + <a class="note-preview" :href="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</a> </div> </template> <template v-if="notification.type == 'poll_vote'"> @@ -80,8 +80,8 @@ </router-link> <div class="text"> <p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p> - <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% + <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% </router-link> </div> </template> @@ -103,7 +103,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; -import getPostSummary from '../../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../../renderers/get-note-summary'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ @@ -115,7 +115,7 @@ export default Vue.extend({ moreNotifications: false, connection: null, connectionId: null, - getPostSummary + getNoteSummary }; }, computed: { @@ -241,10 +241,10 @@ export default Vue.extend({ i, .mk-reaction-icon margin-right 4px - .post-preview + .note-preview color rgba(0, 0, 0, 0.7) - .post-ref + .note-ref color rgba(0, 0, 0, 0.7) [data-fa] @@ -254,7 +254,7 @@ export default Vue.extend({ display inline-block margin-right 3px - &.repost, &.quote + &.renote, &.quote .text p i color #77B255 diff --git a/src/client/app/desktop/views/components/post-detail.vue b/src/client/app/desktop/views/components/post-detail.vue index 1a3c0d1b68..790f797ad6 100644 --- a/src/client/app/desktop/views/components/post-detail.vue +++ b/src/client/app/desktop/views/components/post-detail.vue @@ -1,5 +1,5 @@ <template> -<div class="mk-post-detail" :title="title"> +<div class="mk-note-detail" :title="title"> <button class="read-more" v-if="p.reply && p.reply.replyId && context == null" @@ -11,19 +11,19 @@ <template v-if="contextFetching">%fa:spinner .pulse%</template> </button> <div class="context"> - <x-sub v-for="post in context" :key="post.id" :post="post"/> + <x-sub v-for="note in context" :key="note.id" :note="note"/> </div> <div class="reply-to" v-if="p.reply"> - <x-sub :post="p.reply"/> + <x-sub :note="p.reply"/> </div> - <div class="repost" v-if="isRepost"> + <div class="renote" v-if="isRenote"> <p> - <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> </router-link> %fa:retweet% - <router-link class="name" :href="`/@${acct}`">{{ getUserName(post.user) }}</router-link> - がRepost + <router-link class="name" :href="`/@${acct}`">{{ getUserName(note.user) }}</router-link> + がRenote </p> </div> <article> @@ -38,28 +38,28 @@ </router-link> </header> <div class="body"> - <mk-post-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> + <mk-note-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> - <mk-poll v-if="p.poll" :post="p"/> + <mk-poll v-if="p.poll" :note="p"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> <div class="tags" v-if="p.tags && p.tags.length > 0"> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> </div> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> <div class="map" v-if="p.geo" ref="map"></div> - <div class="repost" v-if="p.repost"> - <mk-post-preview :post="p.repost"/> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> </div> </div> <footer> - <mk-reactions-viewer :post="p"/> + <mk-reactions-viewer :note="p"/> <button @click="reply" title="返信"> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> </button> - <button @click="repost" title="Repost"> - %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> </button> <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="リアクション"> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> @@ -70,7 +70,7 @@ </footer> </article> <div class="replies" v-if="!compact"> - <x-sub v-for="post in replies" :key="post.id" :post="post"/> + <x-sub v-for="note in replies" :key="note.id" :note="note"/> </div> </div> </template> @@ -83,10 +83,10 @@ import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostFormWindow from './post-form-window.vue'; -import MkRepostFormWindow from './repost-form-window.vue'; -import MkPostMenu from '../../../common/views/components/post-menu.vue'; +import MkRenoteFormWindow from './renote-form-window.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; -import XSub from './post-detail.sub.vue'; +import XSub from './note-detail.sub.vue'; export default Vue.extend({ components: { @@ -94,7 +94,7 @@ export default Vue.extend({ }, props: { - post: { + note: { type: Object, required: true }, @@ -112,14 +112,14 @@ export default Vue.extend({ }, computed: { - isRepost(): boolean { - return (this.post.repost && - this.post.text == null && - this.post.mediaIds == null && - this.post.poll == null); + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); }, p(): any { - return this.isRepost ? this.post.repost : this.post; + return this.isRenote ? this.note.renote : this.note; }, reactionsCount(): number { return this.p.reactionCounts @@ -132,10 +132,10 @@ export default Vue.extend({ return dateStringify(this.p.createdAt); }, acct(): string { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name(): string { - return getUserName(this.post.user); + return getUserName(this.note.user); }, pAcct(): string { return getAcct(this.p.user); @@ -158,8 +158,8 @@ export default Vue.extend({ mounted() { // Get replies if (!this.compact) { - (this as any).api('posts/replies', { - postId: this.p.id, + (this as any).api('notes/replies', { + noteId: this.p.id, limit: 8 }).then(replies => { this.replies = replies; @@ -190,8 +190,8 @@ export default Vue.extend({ this.contextFetching = true; // Fetch context - (this as any).api('posts/context', { - postId: this.p.replyId + (this as any).api('notes/context', { + noteId: this.p.replyId }).then(context => { this.contextFetching = false; this.context = context.reverse(); @@ -202,21 +202,21 @@ export default Vue.extend({ reply: this.p }); }, - repost() { - (this as any).os.new(MkRepostFormWindow, { - post: this.p + renote() { + (this as any).os.new(MkRenoteFormWindow, { + note: this.p }); }, react() { (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, - post: this.p + note: this.p }); }, menu() { - (this as any).os.new(MkPostMenu, { + (this as any).os.new(MkNoteMenu, { source: this.$refs.menuButton, - post: this.p + note: this.p }); } } @@ -226,7 +226,7 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.mk-post-detail +.mk-note-detail margin 0 padding 0 overflow hidden @@ -263,7 +263,7 @@ export default Vue.extend({ > * border-bottom 1px solid #eef0f2 - > .repost + > .renote color #9dbb00 background linear-gradient(to bottom, #edfde2 0%, #fff 100%) @@ -355,10 +355,10 @@ export default Vue.extend({ > .body padding 8px 0 - > .repost + > .renote margin 8px 0 - > .mk-post-preview + > .mk-note-preview padding 16px border dashed 1px #c0dac6 border-radius 8px diff --git a/src/client/app/desktop/views/components/post-form-window.vue b/src/client/app/desktop/views/components/post-form-window.vue index d0b115e852..c890592a5e 100644 --- a/src/client/app/desktop/views/components/post-form-window.vue +++ b/src/client/app/desktop/views/components/post-form-window.vue @@ -2,13 +2,13 @@ <mk-window ref="window" is-modal @closed="$destroy"> <span slot="header"> <span :class="$style.icon" v-if="geo">%fa:map-marker-alt%</span> - <span v-if="!reply">%i18n:desktop.tags.mk-post-form-window.post%</span> + <span v-if="!reply">%i18n:desktop.tags.mk-post-form-window.note%</span> <span v-if="reply">%i18n:desktop.tags.mk-post-form-window.reply%</span> <span :class="$style.count" v-if="media.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.attaches%'.replace('{}', media.length) }}</span> <span :class="$style.count" v-if="uploadings.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span> </span> - <mk-post-preview v-if="reply" :class="$style.postPreview" :post="reply"/> + <mk-note-preview v-if="reply" :class="$style.notePreview" :note="reply"/> <mk-post-form ref="form" :reply="reply" @posted="onPosted" @@ -70,7 +70,7 @@ export default Vue.extend({ &:after content ')' -.postPreview +.notePreview margin 16px 22px </style> diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 1c83a38b64..8e3ec24bc8 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -46,7 +46,7 @@ export default Vue.extend({ components: { XDraggable }, - props: ['reply', 'repost'], + props: ['reply', 'renote'], data() { return { posting: false, @@ -61,28 +61,28 @@ export default Vue.extend({ }, computed: { draftId(): string { - return this.repost - ? 'repost:' + this.repost.id + return this.renote + ? 'renote:' + this.renote.id : this.reply ? 'reply:' + this.reply.id - : 'post'; + : 'note'; }, placeholder(): string { - return this.repost + return this.renote ? '%i18n:desktop.tags.mk-post-form.quote-placeholder%' : this.reply ? '%i18n:desktop.tags.mk-post-form.reply-placeholder%' - : '%i18n:desktop.tags.mk-post-form.post-placeholder%'; + : '%i18n:desktop.tags.mk-post-form.note-placeholder%'; }, submitText(): string { - return this.repost - ? '%i18n:desktop.tags.mk-post-form.repost%' + return this.renote + ? '%i18n:desktop.tags.mk-post-form.renote%' : this.reply ? '%i18n:desktop.tags.mk-post-form.reply%' - : '%i18n:desktop.tags.mk-post-form.post%'; + : '%i18n:desktop.tags.mk-post-form.note%'; }, canPost(): boolean { - return !this.posting && (this.text.length != 0 || this.files.length != 0 || this.poll || this.repost); + return !this.posting && (this.text.length != 0 || this.files.length != 0 || this.poll || this.renote); } }, watch: { @@ -217,11 +217,11 @@ export default Vue.extend({ post() { this.posting = true; - (this as any).api('posts/create', { + (this as any).api('notes/create', { text: this.text == '' ? undefined : this.text, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, - repostId: this.repost ? this.repost.id : undefined, + renoteId: this.renote ? this.renote.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, geo: this.geo ? { coordinates: [this.geo.longitude, this.geo.latitude], @@ -235,17 +235,17 @@ export default Vue.extend({ this.clear(); this.deleteDraft(); this.$emit('posted'); - (this as any).apis.notify(this.repost + (this as any).apis.notify(this.renote ? '%i18n:desktop.tags.mk-post-form.reposted%' : this.reply ? '%i18n:desktop.tags.mk-post-form.replied%' : '%i18n:desktop.tags.mk-post-form.posted%'); }).catch(err => { - (this as any).apis.notify(this.repost - ? '%i18n:desktop.tags.mk-post-form.repost-failed%' + (this as any).apis.notify(this.renote + ? '%i18n:desktop.tags.mk-post-form.renote-failed%' : this.reply ? '%i18n:desktop.tags.mk-post-form.reply-failed%' - : '%i18n:desktop.tags.mk-post-form.post-failed%'); + : '%i18n:desktop.tags.mk-post-form.note-failed%'); }).then(() => { this.posting = false; }); diff --git a/src/client/app/desktop/views/components/posts.post.vue b/src/client/app/desktop/views/components/posts.post.vue index 17fe330420..924642c01a 100644 --- a/src/client/app/desktop/views/components/posts.post.vue +++ b/src/client/app/desktop/views/components/posts.post.vue @@ -1,19 +1,19 @@ <template> -<div class="post" tabindex="-1" :title="title" @keydown="onKeydown"> +<div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> <div class="reply-to" v-if="p.reply"> - <x-sub :post="p.reply"/> + <x-sub :note="p.reply"/> </div> - <div class="repost" v-if="isRepost"> + <div class="renote" v-if="isRenote"> <p> - <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> </router-link> %fa:retweet% - <span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> - <a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</a> - <span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> + <span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> + <a class="name" :href="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a> + <span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> </p> - <mk-time :time="post.createdAt"/> + <mk-time :time="note.createdAt"/> </div> <article> <router-link class="avatar-anchor" :to="`/@${acct}`"> @@ -38,38 +38,38 @@ </p> <div class="text"> <a class="reply" v-if="p.reply">%fa:reply%</a> - <mk-post-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> - <a class="rp" v-if="p.repost">RP:</a> + <mk-note-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> + <a class="rp" v-if="p.renote">RP:</a> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> - <mk-poll v-if="p.poll" :post="p" ref="pollViewer"/> + <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> <div class="tags" v-if="p.tags && p.tags.length > 0"> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> </div> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> <div class="map" v-if="p.geo" ref="map"></div> - <div class="repost" v-if="p.repost"> - <mk-post-preview :post="p.repost"/> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> </div> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> </div> <footer> - <mk-reactions-viewer :post="p" ref="reactionsViewer"/> - <button @click="reply" title="%i18n:desktop.tags.mk-timeline-post.reply%"> + <mk-reactions-viewer :note="p" ref="reactionsViewer"/> + <button @click="reply" title="%i18n:desktop.tags.mk-timeline-note.reply%"> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> </button> - <button @click="repost" title="%i18n:desktop.tags.mk-timeline-post.repost%"> - %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> + <button @click="renote" title="%i18n:desktop.tags.mk-timeline-note.renote%"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> </button> - <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%"> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-note.add-reaction%"> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> </button> <button @click="menu" ref="menuButton"> %fa:ellipsis-h% </button> - <button title="%i18n:desktop.tags.mk-timeline-post.detail"> + <button title="%i18n:desktop.tags.mk-timeline-note.detail"> <template v-if="!isDetailOpened">%fa:caret-down%</template> <template v-if="isDetailOpened">%fa:caret-up%</template> </button> @@ -77,7 +77,7 @@ </div> </article> <div class="detail" v-if="isDetailOpened"> - <mk-post-status-graph width="462" height="130" :post="p"/> + <mk-note-status-graph width="462" height="130" :note="p"/> </div> </div> </template> @@ -90,10 +90,10 @@ import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostFormWindow from './post-form-window.vue'; -import MkRepostFormWindow from './repost-form-window.vue'; -import MkPostMenu from '../../../common/views/components/post-menu.vue'; +import MkRenoteFormWindow from './renote-form-window.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; -import XSub from './posts.post.sub.vue'; +import XSub from './notes.note.sub.vue'; function focus(el, fn) { const target = fn(el); @@ -111,7 +111,7 @@ export default Vue.extend({ XSub }, - props: ['post'], + props: ['note'], data() { return { @@ -128,14 +128,14 @@ export default Vue.extend({ name(): string { return getUserName(this.p.user); }, - isRepost(): boolean { - return (this.post.repost && - this.post.text == null && - this.post.mediaIds == null && - this.post.poll == null); + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); }, p(): any { - return this.isRepost ? this.post.repost : this.post; + return this.isRenote ? this.note.renote : this.note; }, reactionsCount(): number { return this.p.reactionCounts @@ -211,7 +211,7 @@ export default Vue.extend({ type: 'capture', id: this.p.id }); - if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated); + if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); } }, decapture(withHandler = false) { @@ -220,18 +220,18 @@ export default Vue.extend({ type: 'decapture', id: this.p.id }); - if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); + if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); } }, onStreamConnected() { this.capture(); }, - onStreamPostUpdated(data) { - const post = data.post; - if (post.id == this.post.id) { - this.$emit('update:post', post); - } else if (post.id == this.post.repostId) { - this.post.repost = post; + onStreamNoteUpdated(data) { + const note = data.note; + if (note.id == this.note.id) { + this.$emit('update:note', note); + } else if (note.id == this.note.renoteId) { + this.note.renote = note; } }, reply() { @@ -239,21 +239,21 @@ export default Vue.extend({ reply: this.p }); }, - repost() { - (this as any).os.new(MkRepostFormWindow, { - post: this.p + renote() { + (this as any).os.new(MkRenoteFormWindow, { + note: this.p }); }, react() { (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, - post: this.p + note: this.p }); }, menu() { - (this as any).os.new(MkPostMenu, { + (this as any).os.new(MkNoteMenu, { source: this.$refs.menuButton, - post: this.p + note: this.p }); }, onKeydown(e) { @@ -274,7 +274,7 @@ export default Vue.extend({ case e.which == 81: // [q] case e.which == 69: // [e] - this.repost(); + this.renote(); break; case e.which == 70: // [f] @@ -299,7 +299,7 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.post +.note margin 0 padding 0 background #fff @@ -309,7 +309,7 @@ export default Vue.extend({ border-top-left-radius 6px border-top-right-radius 6px - > .repost + > .renote border-top-left-radius 6px border-top-right-radius 6px @@ -330,7 +330,7 @@ export default Vue.extend({ border 2px solid rgba($theme-color, 0.3) border-radius 4px - > .repost + > .renote color #9dbb00 background linear-gradient(to bottom, #edfde2 0%, #fff 100%) @@ -369,7 +369,7 @@ export default Vue.extend({ padding 0 16px background rgba(0, 0, 0, 0.0125) - > .mk-post-preview + > .mk-note-preview background transparent > article @@ -529,10 +529,10 @@ export default Vue.extend({ > .mk-poll font-size 80% - > .repost + > .renote margin 8px 0 - > .mk-post-preview + > .mk-note-preview padding 16px border dashed 1px #c0dac6 border-radius 8px diff --git a/src/client/app/desktop/views/components/renote-form-window.vue b/src/client/app/desktop/views/components/renote-form-window.vue new file mode 100644 index 0000000000..09176b5ba7 --- /dev/null +++ b/src/client/app/desktop/views/components/renote-form-window.vue @@ -0,0 +1,42 @@ +<template> +<mk-window ref="window" is-modal @closed="$destroy"> + <span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-renote-form-window.title%</span> + <mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/> +</mk-window> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: ['note'], + mounted() { + document.addEventListener('keydown', this.onDocumentKeydown); + }, + beforeDestroy() { + document.removeEventListener('keydown', this.onDocumentKeydown); + }, + methods: { + onDocumentKeydown(e) { + if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') { + if (e.which == 27) { // Esc + (this.$refs.window as any).close(); + } + } + }, + onPosted() { + (this.$refs.window as any).close(); + }, + onCanceled() { + (this.$refs.window as any).close(); + } + } +}); +</script> + +<style lang="stylus" module> +.header + > [data-fa] + margin-right 4px + +</style> diff --git a/src/client/app/desktop/views/components/renote-form.vue b/src/client/app/desktop/views/components/renote-form.vue new file mode 100644 index 0000000000..c6b9074156 --- /dev/null +++ b/src/client/app/desktop/views/components/renote-form.vue @@ -0,0 +1,131 @@ +<template> +<div class="mk-renote-form"> + <mk-note-preview :note="note"/> + <template v-if="!quote"> + <footer> + <a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-renote-form.quote%</a> + <button class="cancel" @click="cancel">%i18n:desktop.tags.mk-renote-form.cancel%</button> + <button class="ok" @click="ok" :disabled="wait">{{ wait ? '%i18n:desktop.tags.mk-renote-form.reposting%' : '%i18n:desktop.tags.mk-renote-form.renote%' }}</button> + </footer> + </template> + <template v-if="quote"> + <mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/> + </template> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: ['note'], + data() { + return { + wait: false, + quote: false + }; + }, + methods: { + ok() { + this.wait = true; + (this as any).api('notes/create', { + renoteId: this.note.id + }).then(data => { + this.$emit('posted'); + (this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.success%'); + }).catch(err => { + (this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.failure%'); + }).then(() => { + this.wait = false; + }); + }, + cancel() { + this.$emit('canceled'); + }, + onQuote() { + this.quote = true; + + this.$nextTick(() => { + (this.$refs.form as any).focus(); + }); + }, + onChildFormPosted() { + this.$emit('posted'); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.mk-renote-form + + > .mk-note-preview + margin 16px 22px + + > footer + height 72px + background lighten($theme-color, 95%) + + > .quote + position absolute + bottom 16px + left 28px + line-height 40px + + button + display block + position absolute + bottom 16px + cursor pointer + padding 0 + margin 0 + width 120px + height 40px + font-size 1em + outline none + border-radius 4px + + &:focus + &:after + content "" + pointer-events none + position absolute + top -5px + right -5px + bottom -5px + left -5px + border 2px solid rgba($theme-color, 0.3) + border-radius 8px + + > .cancel + right 148px + color #888 + background linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%) + border solid 1px #e2e2e2 + + &:hover + background linear-gradient(to bottom, #f9f9f9 0%, #ececec 100%) + border-color #dcdcdc + + &:active + background #ececec + border-color #dcdcdc + + > .ok + right 16px + font-weight bold + color $theme-color-foreground + background linear-gradient(to bottom, lighten($theme-color, 25%) 0%, lighten($theme-color, 10%) 100%) + border solid 1px lighten($theme-color, 15%) + + &:hover + background linear-gradient(to bottom, lighten($theme-color, 8%) 0%, darken($theme-color, 8%) 100%) + border-color $theme-color + + &:active + background $theme-color + border-color $theme-color + +</style> diff --git a/src/client/app/desktop/views/components/repost-form-window.vue b/src/client/app/desktop/views/components/repost-form-window.vue index 7db5adbff3..09176b5ba7 100644 --- a/src/client/app/desktop/views/components/repost-form-window.vue +++ b/src/client/app/desktop/views/components/repost-form-window.vue @@ -1,7 +1,7 @@ <template> <mk-window ref="window" is-modal @closed="$destroy"> - <span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-repost-form-window.title%</span> - <mk-repost-form ref="form" :post="post" @posted="onPosted" @canceled="onCanceled"/> + <span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-renote-form-window.title%</span> + <mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/> </mk-window> </template> @@ -9,7 +9,7 @@ import Vue from 'vue'; export default Vue.extend({ - props: ['post'], + props: ['note'], mounted() { document.addEventListener('keydown', this.onDocumentKeydown); }, diff --git a/src/client/app/desktop/views/components/repost-form.vue b/src/client/app/desktop/views/components/repost-form.vue index 3a5e3a7c56..c6b9074156 100644 --- a/src/client/app/desktop/views/components/repost-form.vue +++ b/src/client/app/desktop/views/components/repost-form.vue @@ -1,15 +1,15 @@ <template> -<div class="mk-repost-form"> - <mk-post-preview :post="post"/> +<div class="mk-renote-form"> + <mk-note-preview :note="note"/> <template v-if="!quote"> <footer> - <a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-repost-form.quote%</a> - <button class="cancel" @click="cancel">%i18n:desktop.tags.mk-repost-form.cancel%</button> - <button class="ok" @click="ok" :disabled="wait">{{ wait ? '%i18n:desktop.tags.mk-repost-form.reposting%' : '%i18n:desktop.tags.mk-repost-form.repost%' }}</button> + <a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-renote-form.quote%</a> + <button class="cancel" @click="cancel">%i18n:desktop.tags.mk-renote-form.cancel%</button> + <button class="ok" @click="ok" :disabled="wait">{{ wait ? '%i18n:desktop.tags.mk-renote-form.reposting%' : '%i18n:desktop.tags.mk-renote-form.renote%' }}</button> </footer> </template> <template v-if="quote"> - <mk-post-form ref="form" :repost="post" @posted="onChildFormPosted"/> + <mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/> </template> </div> </template> @@ -18,7 +18,7 @@ import Vue from 'vue'; export default Vue.extend({ - props: ['post'], + props: ['note'], data() { return { wait: false, @@ -28,13 +28,13 @@ export default Vue.extend({ methods: { ok() { this.wait = true; - (this as any).api('posts/create', { - repostId: this.post.id + (this as any).api('notes/create', { + renoteId: this.note.id }).then(data => { this.$emit('posted'); - (this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.success%'); + (this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.success%'); }).catch(err => { - (this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.failure%'); + (this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.failure%'); }).then(() => { this.wait = false; }); @@ -59,9 +59,9 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.mk-repost-form +.mk-renote-form - > .mk-post-preview + > .mk-note-preview margin 16px 22px > footer diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue new file mode 100644 index 0000000000..51ee93cba6 --- /dev/null +++ b/src/client/app/desktop/views/components/sub-note-content.vue @@ -0,0 +1,44 @@ +<template> +<div class="mk-sub-note-content"> + <div class="body"> + <a class="reply" v-if="note.replyId">%fa:reply%</a> + <mk-note-html :text="note.text" :i="os.i"/> + <a class="rp" v-if="note.renoteId" :href="`/note:${note.renoteId}`">RP: ...</a> + </div> + <details v-if="note.media.length > 0"> + <summary>({{ note.media.length }}つのメディア)</summary> + <mk-media-list :media-list="note.media"/> + </details> + <details v-if="note.poll"> + <summary>投票</summary> + <mk-poll :note="note"/> + </details> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: ['note'] +}); +</script> + +<style lang="stylus" scoped> +.mk-sub-note-content + overflow-wrap break-word + + > .body + > .reply + margin-right 6px + color #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + mk-poll + font-size 80% + +</style> diff --git a/src/client/app/desktop/views/components/sub-post-content.vue b/src/client/app/desktop/views/components/sub-post-content.vue deleted file mode 100644 index 17899af280..0000000000 --- a/src/client/app/desktop/views/components/sub-post-content.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<div class="mk-sub-post-content"> - <div class="body"> - <a class="reply" v-if="post.replyId">%fa:reply%</a> - <mk-post-html :text="post.text" :i="os.i"/> - <a class="rp" v-if="post.repostId" :href="`/post:${post.repostId}`">RP: ...</a> - </div> - <details v-if="post.media.length > 0"> - <summary>({{ post.media.length }}つのメディア)</summary> - <mk-media-list :media-list="post.media"/> - </details> - <details v-if="post.poll"> - <summary>投票</summary> - <mk-poll :post="post"/> - </details> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; - -export default Vue.extend({ - props: ['post'] -}); -</script> - -<style lang="stylus" scoped> -.mk-sub-post-content - overflow-wrap break-word - - > .body - > .reply - margin-right 6px - color #717171 - - > .rp - margin-left 4px - font-style oblique - color #a0bf46 - - mk-poll - font-size 80% - -</style> diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue index 65b4bd1c7a..6d049eee9c 100644 --- a/src/client/app/desktop/views/components/timeline.vue +++ b/src/client/app/desktop/views/components/timeline.vue @@ -4,15 +4,15 @@ <div class="fetching" v-if="fetching"> <mk-ellipsis-icon/> </div> - <p class="empty" v-if="posts.length == 0 && !fetching"> + <p class="empty" v-if="notes.length == 0 && !fetching"> %fa:R comments%自分の投稿や、自分がフォローしているユーザーの投稿が表示されます。 </p> - <mk-posts :posts="posts" ref="timeline"> + <mk-notes :notes="notes" ref="timeline"> <button slot="footer" @click="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <template v-if="!moreFetching">もっと見る</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template> </button> - </mk-posts> + </mk-notes> </div> </template> @@ -26,7 +26,7 @@ export default Vue.extend({ fetching: true, moreFetching: false, existMore: false, - posts: [], + notes: [], connection: null, connectionId: null, date: null @@ -41,7 +41,7 @@ export default Vue.extend({ this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); - this.connection.on('post', this.onPost); + this.connection.on('note', this.onNote); this.connection.on('follow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing); @@ -51,7 +51,7 @@ export default Vue.extend({ this.fetch(); }, beforeDestroy() { - this.connection.off('post', this.onPost); + this.connection.off('note', this.onNote); this.connection.off('follow', this.onChangeFollowing); this.connection.off('unfollow', this.onChangeFollowing); (this as any).os.stream.dispose(this.connectionId); @@ -63,45 +63,45 @@ export default Vue.extend({ fetch(cb?) { this.fetching = true; - (this as any).api('posts/timeline', { + (this as any).api('notes/timeline', { limit: 11, untilDate: this.date ? this.date.getTime() : undefined - }).then(posts => { - if (posts.length == 11) { - posts.pop(); + }).then(notes => { + if (notes.length == 11) { + notes.pop(); this.existMore = true; } - this.posts = posts; + this.notes = notes; this.fetching = false; this.$emit('loaded'); if (cb) cb(); }); }, more() { - if (this.moreFetching || this.fetching || this.posts.length == 0 || !this.existMore) return; + if (this.moreFetching || this.fetching || this.notes.length == 0 || !this.existMore) return; this.moreFetching = true; - (this as any).api('posts/timeline', { + (this as any).api('notes/timeline', { limit: 11, - untilId: this.posts[this.posts.length - 1].id - }).then(posts => { - if (posts.length == 11) { - posts.pop(); + untilId: this.notes[this.notes.length - 1].id + }).then(notes => { + if (notes.length == 11) { + notes.pop(); } else { this.existMore = false; } - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); this.moreFetching = false; }); }, - onPost(post) { + onNote(note) { // サウンドを再生する if ((this as any).os.isEnableSounds) { - const sound = new Audio(`${url}/assets/post.mp3`); + const sound = new Audio(`${url}/assets/note.mp3`); sound.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1; sound.play(); } - this.posts.unshift(post); + this.notes.unshift(note); }, onChangeFollowing() { this.fetch(); diff --git a/src/client/app/desktop/views/components/ui.header.post.vue b/src/client/app/desktop/views/components/ui.header.post.vue index c2f0e07dd3..5c1756b756 100644 --- a/src/client/app/desktop/views/components/ui.header.post.vue +++ b/src/client/app/desktop/views/components/ui.header.post.vue @@ -1,6 +1,6 @@ <template> -<div class="post"> - <button @click="post" title="%i18n:desktop.tags.mk-ui-header-post-button.post%">%fa:pencil-alt%</button> +<div class="note"> + <button @click="post" title="%i18n:desktop.tags.mk-ui-header-note-button.note%">%fa:pencil-alt%</button> </div> </template> @@ -19,7 +19,7 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.post +.note display inline-block padding 8px height 100% diff --git a/src/client/app/desktop/views/components/user-preview.vue b/src/client/app/desktop/views/components/user-preview.vue index 2f2e78ec6d..3cbaa2816e 100644 --- a/src/client/app/desktop/views/components/user-preview.vue +++ b/src/client/app/desktop/views/components/user-preview.vue @@ -12,7 +12,7 @@ <div class="description">{{ u.description }}</div> <div class="status"> <div> - <p>投稿</p><a>{{ u.postsCount }}</a> + <p>投稿</p><a>{{ u.notesCount }}</a> </div> <div> <p>フォロー</p><a>{{ u.followingCount }}</a> diff --git a/src/client/app/desktop/views/pages/home.vue b/src/client/app/desktop/views/pages/home.vue index c209af4e43..e4caa2022e 100644 --- a/src/client/app/desktop/views/pages/home.vue +++ b/src/client/app/desktop/views/pages/home.vue @@ -7,7 +7,7 @@ <script lang="ts"> import Vue from 'vue'; import Progress from '../../../common/scripts/loading'; -import getPostSummary from '../../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../../renderers/get-note-summary'; export default Vue.extend({ props: { @@ -29,13 +29,13 @@ export default Vue.extend({ this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); - this.connection.on('post', this.onStreamPost); + this.connection.on('note', this.onStreamNote); document.addEventListener('visibilitychange', this.onVisibilitychange, false); Progress.start(); }, beforeDestroy() { - this.connection.off('post', this.onStreamPost); + this.connection.off('note', this.onStreamNote); (this as any).os.stream.dispose(this.connectionId); document.removeEventListener('visibilitychange', this.onVisibilitychange); }, @@ -44,10 +44,10 @@ export default Vue.extend({ Progress.done(); }, - onStreamPost(post) { - if (document.hidden && post.userId != (this as any).os.i.id) { + onStreamNote(note) { + if (document.hidden && note.userId != (this as any).os.i.id) { this.unreadCount++; - document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; + document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`; } }, diff --git a/src/client/app/desktop/views/pages/post.vue b/src/client/app/desktop/views/pages/note.vue index dbd707e049..17c2b1e954 100644 --- a/src/client/app/desktop/views/pages/post.vue +++ b/src/client/app/desktop/views/pages/note.vue @@ -1,9 +1,9 @@ <template> <mk-ui> <main v-if="!fetching"> - <a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:desktop.tags.mk-post-page.next%</a> - <mk-post-detail :post="post"/> - <a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:desktop.tags.mk-post-page.prev%</a> + <a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:desktop.tags.mk-note-page.next%</a> + <mk-note-detail :note="note"/> + <a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:desktop.tags.mk-note-page.prev%</a> </main> </mk-ui> </template> @@ -16,7 +16,7 @@ export default Vue.extend({ data() { return { fetching: true, - post: null + note: null }; }, watch: { @@ -30,10 +30,10 @@ export default Vue.extend({ Progress.start(); this.fetching = true; - (this as any).api('posts/show', { - postId: this.$route.params.post - }).then(post => { - this.post = post; + (this as any).api('notes/show', { + noteId: this.$route.params.note + }).then(note => { + this.note = note; this.fetching = false; Progress.done(); @@ -60,7 +60,7 @@ main > [data-fa] margin-right 4px - > .mk-post-detail + > .mk-note-detail margin 0 auto width 640px diff --git a/src/client/app/desktop/views/pages/search.vue b/src/client/app/desktop/views/pages/search.vue index afd37c8cee..698154e667 100644 --- a/src/client/app/desktop/views/pages/search.vue +++ b/src/client/app/desktop/views/pages/search.vue @@ -7,12 +7,12 @@ <mk-ellipsis-icon/> </div> <p :class="$style.empty" v-if="!fetching && empty">%fa:search%「{{ q }}」に関する投稿は見つかりませんでした。</p> - <mk-posts ref="timeline" :class="$style.posts" :posts="posts"> + <mk-notes ref="timeline" :class="$style.notes" :notes="notes"> <div slot="footer"> <template v-if="!moreFetching">%fa:search%</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template> </div> - </mk-posts> + </mk-notes> </mk-ui> </template> @@ -30,7 +30,7 @@ export default Vue.extend({ moreFetching: false, existMore: false, offset: 0, - posts: [] + notes: [] }; }, watch: { @@ -38,7 +38,7 @@ export default Vue.extend({ }, computed: { empty(): boolean { - return this.posts.length == 0; + return this.notes.length == 0; }, q(): string { return this.$route.query.q; @@ -66,33 +66,33 @@ export default Vue.extend({ this.fetching = true; Progress.start(); - (this as any).api('posts/search', Object.assign({ + (this as any).api('notes/search', Object.assign({ limit: limit + 1, offset: this.offset - }, parse(this.q))).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }, parse(this.q))).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } - this.posts = posts; + this.notes = notes; this.fetching = false; Progress.done(); }); }, more() { - if (this.moreFetching || this.fetching || this.posts.length == 0 || !this.existMore) return; + if (this.moreFetching || this.fetching || this.notes.length == 0 || !this.existMore) return; this.offset += limit; this.moreFetching = true; - return (this as any).api('posts/search', Object.assign({ + return (this as any).api('notes/search', Object.assign({ limit: limit + 1, offset: this.offset - }, parse(this.q))).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }, parse(this.q))).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); } else { this.existMore = false; } - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); this.moreFetching = false; }); }, @@ -111,7 +111,7 @@ export default Vue.extend({ margin 0 auto color #555 -.posts +.notes max-width 600px margin 0 auto border solid 1px rgba(0, 0, 0, 0.075) diff --git a/src/client/app/desktop/views/pages/user/user.home.vue b/src/client/app/desktop/views/pages/user/user.home.vue index 071c9bb61c..ed3b1c7104 100644 --- a/src/client/app/desktop/views/pages/user/user.home.vue +++ b/src/client/app/desktop/views/pages/user/user.home.vue @@ -9,7 +9,7 @@ </div> </div> <main> - <mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/> + <mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> <x-timeline class="timeline" ref="tl" :user="user"/> </main> <div> diff --git a/src/client/app/desktop/views/pages/user/user.photos.vue b/src/client/app/desktop/views/pages/user/user.photos.vue index 1ff79b4aee..99a1a8d707 100644 --- a/src/client/app/desktop/views/pages/user/user.photos.vue +++ b/src/client/app/desktop/views/pages/user/user.photos.vue @@ -22,13 +22,13 @@ export default Vue.extend({ }; }, mounted() { - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, withMedia: true, limit: 9 - }).then(posts => { - posts.forEach(post => { - post.media.forEach(media => { + }).then(notes => { + notes.forEach(note => { + note.media.forEach(media => { if (this.images.length < 9) this.images.push(media); }); }); diff --git a/src/client/app/desktop/views/pages/user/user.profile.vue b/src/client/app/desktop/views/pages/user/user.profile.vue index f5562d0915..2a82ba7861 100644 --- a/src/client/app/desktop/views/pages/user/user.profile.vue +++ b/src/client/app/desktop/views/pages/user/user.profile.vue @@ -14,7 +14,7 @@ <p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screenName}`" target="_blank">@{{ user.account.twitter.screenName }}</a></p> </div> <div class="status"> - <p class="posts-count">%fa:angle-right%<a>{{ user.postsCount }}</a><b>投稿</b></p> + <p class="notes-count">%fa:angle-right%<a>{{ user.notesCount }}</a><b>投稿</b></p> <p class="following">%fa:angle-right%<a @click="showFollowing">{{ user.followingCount }}</a>人を<b>フォロー</b></p> <p class="followers">%fa:angle-right%<a @click="showFollowers">{{ user.followersCount }}</a>人の<b>フォロワー</b></p> </div> diff --git a/src/client/app/desktop/views/pages/user/user.timeline.vue b/src/client/app/desktop/views/pages/user/user.timeline.vue index 134ad423ce..87d133174b 100644 --- a/src/client/app/desktop/views/pages/user/user.timeline.vue +++ b/src/client/app/desktop/views/pages/user/user.timeline.vue @@ -8,12 +8,12 @@ <mk-ellipsis-icon/> </div> <p class="empty" v-if="empty">%fa:R comments%このユーザーはまだ何も投稿していないようです。</p> - <mk-posts ref="timeline" :posts="posts"> + <mk-notes ref="timeline" :notes="notes"> <div slot="footer"> <template v-if="!moreFetching">%fa:moon%</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template> </div> - </mk-posts> + </mk-notes> </div> </template> @@ -27,7 +27,7 @@ export default Vue.extend({ moreFetching: false, mode: 'default', unreadCount: 0, - posts: [], + notes: [], date: null }; }, @@ -38,7 +38,7 @@ export default Vue.extend({ }, computed: { empty(): boolean { - return this.posts.length == 0; + return this.notes.length == 0; } }, mounted() { @@ -60,26 +60,26 @@ export default Vue.extend({ } }, fetch(cb?) { - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, untilDate: this.date ? this.date.getTime() : undefined, with_replies: this.mode == 'with-replies' - }).then(posts => { - this.posts = posts; + }).then(notes => { + this.notes = notes; this.fetching = false; if (cb) cb(); }); }, more() { - if (this.moreFetching || this.fetching || this.posts.length == 0) return; + if (this.moreFetching || this.fetching || this.notes.length == 0) return; this.moreFetching = true; - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, with_replies: this.mode == 'with-replies', - untilId: this.posts[this.posts.length - 1].id - }).then(posts => { + untilId: this.notes[this.notes.length - 1].id + }).then(notes => { this.moreFetching = false; - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); }); }, onScroll() { diff --git a/src/client/app/desktop/views/widgets/channel.channel.form.vue b/src/client/app/desktop/views/widgets/channel.channel.form.vue index aaf327f1ef..f2744268bb 100644 --- a/src/client/app/desktop/views/widgets/channel.channel.form.vue +++ b/src/client/app/desktop/views/widgets/channel.channel.form.vue @@ -24,11 +24,11 @@ export default Vue.extend({ if (/^>>([0-9]+) /.test(this.text)) { const index = this.text.match(/^>>([0-9]+) /)[1]; - reply = (this.$parent as any).posts.find(p => p.index.toString() == index); + reply = (this.$parent as any).notes.find(p => p.index.toString() == index); this.text = this.text.replace(/^>>([0-9]+) /, ''); } - (this as any).api('posts/create', { + (this as any).api('notes/create', { text: this.text, replyId: reply ? reply.id : undefined, channelId: (this.$parent as any).channel.id diff --git a/src/client/app/desktop/views/widgets/channel.channel.post.vue b/src/client/app/desktop/views/widgets/channel.channel.note.vue index fa6d8c34a5..313a2e3f4f 100644 --- a/src/client/app/desktop/views/widgets/channel.channel.post.vue +++ b/src/client/app/desktop/views/widgets/channel.channel.note.vue @@ -1,15 +1,15 @@ <template> -<div class="post"> +<div class="note"> <header> - <a class="index" @click="reply">{{ post.index }}:</a> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ name }}</b></router-link> + <a class="index" @click="reply">{{ note.index }}:</a> + <router-link class="name" :to="`/@${acct}`" v-user-preview="note.user.id"><b>{{ name }}</b></router-link> <span>ID:<i>{{ acct }}</i></span> </header> <div> - <a v-if="post.reply">>>{{ post.reply.index }}</a> - {{ post.text }} - <div class="media" v-if="post.media"> - <a v-for="file in post.media" :href="file.url" target="_blank"> + <a v-if="note.reply">>>{{ note.reply.index }}</a> + {{ note.text }} + <div class="media" v-if="note.media"> + <a v-for="file in note.media" :href="file.url" target="_blank"> <img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/> </a> </div> @@ -23,25 +23,25 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); } }, methods: { reply() { - this.$emit('reply', this.post); + this.$emit('reply', this.note); } } }); </script> <style lang="stylus" scoped> -.post +.note margin 0 padding 0 color #444 diff --git a/src/client/app/desktop/views/widgets/channel.channel.vue b/src/client/app/desktop/views/widgets/channel.channel.vue index e9fb9e3fd7..ea4d8f8454 100644 --- a/src/client/app/desktop/views/widgets/channel.channel.vue +++ b/src/client/app/desktop/views/widgets/channel.channel.vue @@ -1,9 +1,9 @@ <template> <div class="channel"> <p v-if="fetching">読み込み中<mk-ellipsis/></p> - <div v-if="!fetching" ref="posts" class="posts"> - <p v-if="posts.length == 0">まだ投稿がありません</p> - <x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/> + <div v-if="!fetching" ref="notes" class="notes"> + <p v-if="notes.length == 0">まだ投稿がありません</p> + <x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.id" @reply="reply"/> </div> <x-form class="form" ref="form"/> </div> @@ -13,18 +13,18 @@ import Vue from 'vue'; import ChannelStream from '../../../common/scripts/streaming/channel'; import XForm from './channel.channel.form.vue'; -import XPost from './channel.channel.post.vue'; +import XNote from './channel.channel.note.vue'; export default Vue.extend({ components: { XForm, - XPost + XNote }, props: ['channel'], data() { return { fetching: true, - posts: [], + notes: [], connection: null }; }, @@ -43,10 +43,10 @@ export default Vue.extend({ zap() { this.fetching = true; - (this as any).api('channels/posts', { + (this as any).api('channels/notes', { channelId: this.channel.id - }).then(posts => { - this.posts = posts; + }).then(notes => { + this.notes = notes; this.fetching = false; this.$nextTick(() => { @@ -55,24 +55,24 @@ export default Vue.extend({ this.disconnect(); this.connection = new ChannelStream((this as any).os, this.channel.id); - this.connection.on('post', this.onPost); + this.connection.on('note', this.onNote); }); }, disconnect() { if (this.connection) { - this.connection.off('post', this.onPost); + this.connection.off('note', this.onNote); this.connection.close(); } }, - onPost(post) { - this.posts.unshift(post); + onNote(note) { + this.notes.unshift(note); this.scrollToBottom(); }, scrollToBottom() { - (this.$refs.posts as any).scrollTop = (this.$refs.posts as any).scrollHeight; + (this.$refs.notes as any).scrollTop = (this.$refs.notes as any).scrollHeight; }, - reply(post) { - (this.$refs.form as any).text = `>>${ post.index } `; + reply(note) { + (this.$refs.form as any).text = `>>${ note.index } `; } } }); @@ -87,12 +87,12 @@ export default Vue.extend({ text-align center color #aaa - > .posts + > .notes height calc(100% - 38px) overflow auto font-size 0.9em - > .post + > .note border-bottom solid 1px #eee &:last-child diff --git a/src/client/app/desktop/views/widgets/polls.vue b/src/client/app/desktop/views/widgets/polls.vue index c8ba17bd4b..eb49a4cd5c 100644 --- a/src/client/app/desktop/views/widgets/polls.vue +++ b/src/client/app/desktop/views/widgets/polls.vue @@ -7,7 +7,7 @@ <div class="poll" v-if="!fetching && poll != null"> <p v-if="poll.text"><router-link to="`/@${ acct }/${ poll.id }`">{{ poll.text }}</router-link></p> <p v-if="!poll.text"><router-link to="`/@${ acct }/${ poll.id }`">%fa:link%</router-link></p> - <mk-poll :post="poll"/> + <mk-poll :note="poll"/> </div> <p class="empty" v-if="!fetching && poll == null">%i18n:desktop.tags.mk-recommended-polls-home-widget.nothing%</p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> @@ -47,11 +47,11 @@ export default define({ this.fetching = true; this.poll = null; - (this as any).api('posts/polls/recommendation', { + (this as any).api('notes/polls/recommendation', { limit: 1, offset: this.offset - }).then(posts => { - const poll = posts ? posts[0] : null; + }).then(notes => { + const poll = notes ? notes[0] : null; if (poll == null) { this.offset = 0; } else { diff --git a/src/client/app/desktop/views/widgets/post-form.vue b/src/client/app/desktop/views/widgets/post-form.vue index cf7fd1f2b2..5e59582a0f 100644 --- a/src/client/app/desktop/views/widgets/post-form.vue +++ b/src/client/app/desktop/views/widgets/post-form.vue @@ -4,7 +4,7 @@ <p class="title">%fa:pencil-alt%%i18n:desktop.tags.mk-post-form-home-widget.title%</p> </template> <textarea :disabled="posting" v-model="text" @keydown="onKeydown" placeholder="%i18n:desktop.tags.mk-post-form-home-widget.placeholder%"></textarea> - <button @click="post" :disabled="posting">%i18n:desktop.tags.mk-post-form-home-widget.post%</button> + <button @click="post" :disabled="posting">%i18n:desktop.tags.mk-post-form-home-widget.note%</button> </div> </template> @@ -36,7 +36,7 @@ export default define({ post() { this.posting = true; - (this as any).api('posts/create', { + (this as any).api('notes/create', { text: this.text }).then(data => { this.clear(); diff --git a/src/client/app/desktop/views/widgets/trends.vue b/src/client/app/desktop/views/widgets/trends.vue index 27c1860b32..c2c7636bb3 100644 --- a/src/client/app/desktop/views/widgets/trends.vue +++ b/src/client/app/desktop/views/widgets/trends.vue @@ -5,8 +5,8 @@ <button @click="fetch" title="%i18n:desktop.tags.mk-trends-home-widget.refresh%">%fa:sync%</button> </template> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> - <div class="post" v-else-if="post != null"> - <p class="text"><router-link :to="`/@${ acct }/${ post.id }`">{{ post.text }}</router-link></p> + <div class="note" v-else-if="note != null"> + <p class="text"><router-link :to="`/@${ acct }/${ note.id }`">{{ note.text }}</router-link></p> <p class="author">―<router-link :to="`/@${ acct }`">@{{ acct }}</router-link></p> </div> <p class="empty" v-else>%i18n:desktop.tags.mk-trends-home-widget.nothing%</p> @@ -25,12 +25,12 @@ export default define({ }).extend({ computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, }, data() { return { - post: null, + note: null, fetching: true, offset: 0 }; @@ -44,23 +44,23 @@ export default define({ }, fetch() { this.fetching = true; - this.post = null; + this.note = null; - (this as any).api('posts/trend', { + (this as any).api('notes/trend', { limit: 1, offset: this.offset, - repost: false, + renote: false, reply: false, media: false, poll: false - }).then(posts => { - const post = posts ? posts[0] : null; - if (post == null) { + }).then(notes => { + const note = notes ? notes[0] : null; + if (note == null) { this.offset = 0; } else { this.offset++; } - this.post = post; + this.note = note; this.fetching = false; }); } @@ -103,7 +103,7 @@ export default define({ &:active color #999 - > .post + > .note padding 16px font-size 12px font-style oblique diff --git a/src/client/app/dev/views/new-app.vue b/src/client/app/dev/views/new-app.vue index e407ca00d7..c9d5971395 100644 --- a/src/client/app/dev/views/new-app.vue +++ b/src/client/app/dev/views/new-app.vue @@ -27,7 +27,7 @@ <b-form-checkbox-group v-model="permission" stacked> <b-form-checkbox value="account-read">アカウントの情報を見る。</b-form-checkbox> <b-form-checkbox value="account-write">アカウントの情報を操作する。</b-form-checkbox> - <b-form-checkbox value="post-write">投稿する。</b-form-checkbox> + <b-form-checkbox value="note-write">投稿する。</b-form-checkbox> <b-form-checkbox value="reaction-write">リアクションしたりリアクションをキャンセルする。</b-form-checkbox> <b-form-checkbox value="following-write">フォローしたりフォロー解除する。</b-form-checkbox> <b-form-checkbox value="drive-read">ドライブを見る。</b-form-checkbox> diff --git a/src/client/app/mobile/api/post.ts b/src/client/app/mobile/api/post.ts index 98309ba8de..72919c6505 100644 --- a/src/client/app/mobile/api/post.ts +++ b/src/client/app/mobile/api/post.ts @@ -1,24 +1,24 @@ import PostForm from '../views/components/post-form.vue'; -//import RepostForm from '../views/components/repost-form.vue'; -import getPostSummary from '../../../../renderers/get-post-summary'; +//import RenoteForm from '../views/components/renote-form.vue'; +import getNoteSummary from '../../../../renderers/get-note-summary'; export default (os) => (opts) => { const o = opts || {}; - if (o.repost) { - /*const vm = new RepostForm({ + if (o.renote) { + /*const vm = new RenoteForm({ propsData: { - repost: o.repost + renote: o.renote } }).$mount(); vm.$once('cancel', recover); - vm.$once('post', recover); + vm.$once('note', recover); document.body.appendChild(vm.$el);*/ - const text = window.prompt(`「${getPostSummary(o.repost)}」をRepost`); + const text = window.prompt(`「${getNoteSummary(o.renote)}」をRenote`); if (text == null) return; - os.api('posts/create', { - repostId: o.repost.id, + os.api('notes/create', { + renoteId: o.renote.id, text: text == '' ? undefined : text }); } else { @@ -36,7 +36,7 @@ export default (os) => (opts) => { } }).$mount(); vm.$once('cancel', recover); - vm.$once('post', recover); + vm.$once('note', recover); document.body.appendChild(vm.$el); (vm as any).focus(); } diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts index 4776fccddb..6265d0d45f 100644 --- a/src/client/app/mobile/script.ts +++ b/src/client/app/mobile/script.ts @@ -25,7 +25,7 @@ import MkDrive from './views/pages/drive.vue'; import MkNotifications from './views/pages/notifications.vue'; import MkMessaging from './views/pages/messaging.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue'; -import MkPost from './views/pages/post.vue'; +import MkNote from './views/pages/note.vue'; import MkSearch from './views/pages/search.vue'; import MkFollowers from './views/pages/followers.vue'; import MkFollowing from './views/pages/following.vue'; @@ -68,7 +68,7 @@ init((launch) => { { path: '/@:user', component: MkUser }, { path: '/@:user/followers', component: MkFollowers }, { path: '/@:user/following', component: MkFollowing }, - { path: '/@:user/:post', component: MkPost } + { path: '/@:user/:note', component: MkNote } ] }); diff --git a/src/client/app/mobile/views/components/activity.vue b/src/client/app/mobile/views/components/activity.vue index 2e44017e77..dcd319cb69 100644 --- a/src/client/app/mobile/views/components/activity.vue +++ b/src/client/app/mobile/views/components/activity.vue @@ -2,14 +2,14 @@ <div class="mk-activity"> <svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> <g v-for="(d, i) in data"> - <rect width="0.8" :height="d.postsH" - :x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH" + <rect width="0.8" :height="d.notesH" + :x="i + 0.1" :y="1 - d.notesH - d.repliesH - d.renotesH" fill="#41ddde"/> <rect width="0.8" :height="d.repliesH" - :x="i + 0.1" :y="1 - d.repliesH - d.repostsH" + :x="i + 0.1" :y="1 - d.repliesH - d.renotesH" fill="#f7796c"/> - <rect width="0.8" :height="d.repostsH" - :x="i + 0.1" :y="1 - d.repostsH" + <rect width="0.8" :height="d.renotesH" + :x="i + 0.1" :y="1 - d.renotesH" fill="#a1de41"/> </g> </svg> @@ -32,12 +32,12 @@ export default Vue.extend({ userId: this.user.id, limit: 30 }).then(data => { - data.forEach(d => d.total = d.posts + d.replies + d.reposts); + data.forEach(d => d.total = d.notes + d.replies + d.renotes); this.peak = Math.max.apply(null, data.map(d => d.total)); data.forEach(d => { - d.postsH = d.posts / this.peak; + d.notesH = d.notes / this.peak; d.repliesH = d.replies / this.peak; - d.repostsH = d.reposts / this.peak; + d.renotesH = d.renotes / this.peak; }); data.reverse(); this.data = data; diff --git a/src/client/app/mobile/views/components/index.ts b/src/client/app/mobile/views/components/index.ts index fb8f65f47d..9346700304 100644 --- a/src/client/app/mobile/views/components/index.ts +++ b/src/client/app/mobile/views/components/index.ts @@ -2,16 +2,16 @@ import Vue from 'vue'; import ui from './ui.vue'; import timeline from './timeline.vue'; -import post from './post.vue'; -import posts from './posts.vue'; +import note from './note.vue'; +import notes from './notes.vue'; import mediaImage from './media-image.vue'; import mediaVideo from './media-video.vue'; import drive from './drive.vue'; -import postPreview from './post-preview.vue'; -import subPostContent from './sub-post-content.vue'; -import postCard from './post-card.vue'; +import notePreview from './note-preview.vue'; +import subNoteContent from './sub-note-content.vue'; +import noteCard from './note-card.vue'; import userCard from './user-card.vue'; -import postDetail from './post-detail.vue'; +import noteDetail from './note-detail.vue'; import followButton from './follow-button.vue'; import friendsMaker from './friends-maker.vue'; import notification from './notification.vue'; @@ -25,16 +25,16 @@ import widgetContainer from './widget-container.vue'; Vue.component('mk-ui', ui); Vue.component('mk-timeline', timeline); -Vue.component('mk-post', post); -Vue.component('mk-posts', posts); +Vue.component('mk-note', note); +Vue.component('mk-notes', notes); Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-video', mediaVideo); Vue.component('mk-drive', drive); -Vue.component('mk-post-preview', postPreview); -Vue.component('mk-sub-post-content', subPostContent); -Vue.component('mk-post-card', postCard); +Vue.component('mk-note-preview', notePreview); +Vue.component('mk-sub-note-content', subNoteContent); +Vue.component('mk-note-card', noteCard); Vue.component('mk-user-card', userCard); -Vue.component('mk-post-detail', postDetail); +Vue.component('mk-note-detail', noteDetail); Vue.component('mk-follow-button', followButton); Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-notification', notification); diff --git a/src/client/app/mobile/views/components/post-card.vue b/src/client/app/mobile/views/components/note-card.vue index 68a42ef249..9ad0d3e294 100644 --- a/src/client/app/mobile/views/components/post-card.vue +++ b/src/client/app/mobile/views/components/note-card.vue @@ -1,41 +1,41 @@ <template> -<div class="mk-post-card"> - <a :href="`/@${acct}/${post.id}`"> +<div class="mk-note-card"> + <a :href="`/@${acct}/${note.id}`"> <header> <img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3> </header> <div> {{ text }} </div> - <mk-time :time="post.createdAt"/> + <mk-time :time="note.createdAt"/> </a> </div> </template> <script lang="ts"> import Vue from 'vue'; -import summary from '../../../../../renderers/get-post-summary'; +import summary from '../../../../../renderers/get-note-summary'; import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); }, text(): string { - return summary(this.post); + return summary(this.note); } } }); </script> <style lang="stylus" scoped> -.mk-post-card +.mk-note-card display inline-block width 150px //height 120px diff --git a/src/client/app/mobile/views/components/post-detail.sub.vue b/src/client/app/mobile/views/components/note-detail.sub.vue index 98d6a14cac..38aea4ba20 100644 --- a/src/client/app/mobile/views/components/post-detail.sub.vue +++ b/src/client/app/mobile/views/components/note-detail.sub.vue @@ -1,18 +1,18 @@ <template> <div class="root sub"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link> <span class="username">@{{ acct }}</span> - <router-link class="time" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="time" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </header> <div class="body"> - <mk-sub-post-content class="text" :post="post"/> + <mk-sub-note-content class="text" :note="note"/> </div> </div> </div> @@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); } } }); diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue new file mode 100644 index 0000000000..e1682e58ed --- /dev/null +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -0,0 +1,462 @@ +<template> +<div class="mk-note-detail"> + <button + class="more" + v-if="p.reply && p.reply.replyId && context == null" + @click="fetchContext" + :disabled="fetchingContext" + > + <template v-if="!contextFetching">%fa:ellipsis-v%</template> + <template v-if="contextFetching">%fa:spinner .pulse%</template> + </button> + <div class="context"> + <x-sub v-for="note in context" :key="note.id" :note="note"/> + </div> + <div class="reply-to" v-if="p.reply"> + <x-sub :note="p.reply"/> + </div> + <div class="renote" v-if="isRenote"> + <p> + <router-link class="avatar-anchor" :to="`/@${acct}`"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + </router-link> + %fa:retweet% + <router-link class="name" :to="`/@${acct}`"> + {{ name }} + </router-link> + がRenote + </p> + </div> + <article> + <header> + <router-link class="avatar-anchor" :to="`/@${pAcct}`"> + <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + </router-link> + <div> + <router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link> + <span class="username">@{{ pAcct }}</span> + </div> + </header> + <div class="body"> + <mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/> + <div class="tags" v-if="p.tags && p.tags.length > 0"> + <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> + </div> + <div class="media" v-if="p.media.length > 0"> + <mk-media-list :media-list="p.media"/> + </div> + <mk-poll v-if="p.poll" :note="p"/> + <mk-url-preview v-for="url in urls" :url="url" :key="url"/> + <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> + <div class="map" v-if="p.geo" ref="map"></div> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> + </div> + </div> + <router-link class="time" :to="`/@${pAcct}/${p.id}`"> + <mk-time :time="p.createdAt" mode="detail"/> + </router-link> + <footer> + <mk-reactions-viewer :note="p"/> + <button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%"> + %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> + </button> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> + </button> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%"> + %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> + </button> + <button @click="menu" ref="menuButton"> + %fa:ellipsis-h% + </button> + </footer> + </article> + <div class="replies" v-if="!compact"> + <x-sub v-for="note in replies" :key="note.id" :note="note"/> + </div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; +import parse from '../../../../../text/parse'; + +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; +import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; +import XSub from './note-detail.sub.vue'; + +export default Vue.extend({ + components: { + XSub + }, + + props: { + note: { + type: Object, + required: true + }, + compact: { + default: false + } + }, + + data() { + return { + context: [], + contextFetching: false, + replies: [] + }; + }, + + computed: { + acct(): string { + return getAcct(this.note.user); + }, + name(): string { + return getUserName(this.note.user); + }, + pAcct(): string { + return getAcct(this.p.user); + }, + pName(): string { + return getUserName(this.p.user); + }, + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); + }, + p(): any { + return this.isRenote ? this.note.renote : this.note; + }, + reactionsCount(): number { + return this.p.reactionCounts + ? Object.keys(this.p.reactionCounts) + .map(key => this.p.reactionCounts[key]) + .reduce((a, b) => a + b) + : 0; + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } + } + }, + + mounted() { + // Get replies + if (!this.compact) { + (this as any).api('notes/replies', { + noteId: this.p.id, + limit: 8 + }).then(replies => { + this.replies = replies; + }); + } + + // Draw map + if (this.p.geo) { + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true; + if (shouldShowMap) { + (this as any).os.getGoogleMaps().then(maps => { + const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); + const map = new maps.Map(this.$refs.map, { + center: uluru, + zoom: 15 + }); + new maps.Marker({ + position: uluru, + map: map + }); + }); + } + } + }, + + methods: { + fetchContext() { + this.contextFetching = true; + + // Fetch context + (this as any).api('notes/context', { + noteId: this.p.replyId + }).then(context => { + this.contextFetching = false; + this.context = context.reverse(); + }); + }, + reply() { + (this as any).apis.post({ + reply: this.p + }); + }, + renote() { + (this as any).apis.post({ + renote: this.p + }); + }, + react() { + (this as any).os.new(MkReactionPicker, { + source: this.$refs.reactButton, + note: this.p, + compact: true + }); + }, + menu() { + (this as any).os.new(MkNoteMenu, { + source: this.$refs.menuButton, + note: this.p, + compact: true + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.mk-note-detail + overflow hidden + margin 0 auto + padding 0 + width 100% + text-align left + background #fff + border-radius 8px + box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) + + > .fetching + padding 64px 0 + + > .more + display block + margin 0 + padding 10px 0 + width 100% + font-size 1em + text-align center + color #999 + cursor pointer + background #fafafa + outline none + border none + border-bottom solid 1px #eef0f2 + border-radius 6px 6px 0 0 + box-shadow none + + &:hover + background #f6f6f6 + + &:active + background #f0f0f0 + + &:disabled + color #ccc + + > .context + > * + border-bottom 1px solid #eef0f2 + + > .renote + color #9dbb00 + background linear-gradient(to bottom, #edfde2 0%, #fff 100%) + + > p + margin 0 + padding 16px 32px + + .avatar-anchor + display inline-block + + .avatar + vertical-align bottom + min-width 28px + min-height 28px + max-width 28px + max-height 28px + margin 0 8px 0 0 + border-radius 6px + + [data-fa] + margin-right 4px + + .name + font-weight bold + + & + article + padding-top 8px + + > .reply-to + border-bottom 1px solid #eef0f2 + + > article + padding 14px 16px 9px 16px + + @media (min-width 500px) + padding 28px 32px 18px 32px + + &:after + content "" + display block + clear both + + &:hover + > .main > footer > button + color #888 + + > header + display flex + line-height 1.1 + + > .avatar-anchor + display block + padding 0 .5em 0 0 + + > .avatar + display block + width 54px + height 54px + margin 0 + border-radius 8px + vertical-align bottom + + @media (min-width 500px) + width 60px + height 60px + + > div + + > .name + display inline-block + margin .4em 0 + color #777 + font-size 16px + font-weight bold + text-align left + text-decoration none + + &:hover + text-decoration underline + + > .username + display block + text-align left + margin 0 + color #ccc + + > .body + padding 8px 0 + + > .renote + margin 8px 0 + + > .mk-note-preview + padding 16px + border dashed 1px #c0dac6 + border-radius 8px + + > .location + margin 4px 0 + font-size 12px + color #ccc + + > .map + width 100% + height 200px + + &:empty + display none + + > .mk-url-preview + margin-top 8px + + > .media + > img + display block + max-width 100% + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + > .time + font-size 16px + color #c0c0c0 + + > footer + font-size 1.2em + + > button + margin 0 + padding 8px + background transparent + border none + box-shadow none + font-size 1em + color #ddd + cursor pointer + + &:not(:last-child) + margin-right 28px + + &:hover + color #666 + + > .count + display inline + margin 0 0 0 8px + color #999 + + &.reacted + color $theme-color + + > .replies + > * + border-top 1px solid #eef0f2 + +</style> + +<style lang="stylus" module> +.text + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 16px + color #717171 + + @media (min-width 500px) + font-size 24px + +</style> diff --git a/src/client/app/mobile/views/components/post-preview.vue b/src/client/app/mobile/views/components/note-preview.vue index 96bd4d5c15..8c8d8645bb 100644 --- a/src/client/app/mobile/views/components/post-preview.vue +++ b/src/client/app/mobile/views/components/note-preview.vue @@ -1,18 +1,18 @@ <template> -<div class="mk-post-preview"> +<div class="mk-note-preview"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> </router-link> <div class="main"> <header> <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> <span class="username">@{{ acct }}</span> - <router-link class="time" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="time" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </header> <div class="body"> - <mk-sub-post-content class="text" :post="post"/> + <mk-sub-note-content class="text" :note="note"/> </div> </div> </div> @@ -24,20 +24,20 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); } } }); </script> <style lang="stylus" scoped> -.mk-post-preview +.mk-note-preview margin 0 padding 0 font-size 0.9em diff --git a/src/client/app/mobile/views/components/post.sub.vue b/src/client/app/mobile/views/components/note.sub.vue index 909d5cb597..a37d0dea08 100644 --- a/src/client/app/mobile/views/components/post.sub.vue +++ b/src/client/app/mobile/views/components/note.sub.vue @@ -1,18 +1,18 @@ <template> <div class="sub"> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link> <span class="username">@{{ acct }}</span> - <router-link class="created-at" :to="`/@${acct}/${post.id}`"> - <mk-time :time="post.createdAt"/> + <router-link class="created-at" :to="`/@${acct}/${note.id}`"> + <mk-time :time="note.createdAt"/> </router-link> </header> <div class="body"> - <mk-sub-post-content class="text" :post="post"/> + <mk-sub-note-content class="text" :note="note"/> </div> </div> </div> @@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ - props: ['post'], + props: ['note'], computed: { acct() { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name() { - return getUserName(this.post.user); + return getUserName(this.note.user); } } }); diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue new file mode 100644 index 0000000000..4b33c6f071 --- /dev/null +++ b/src/client/app/mobile/views/components/note.vue @@ -0,0 +1,540 @@ +<template> +<div class="note" :class="{ renote: isRenote }"> + <div class="reply-to" v-if="p.reply"> + <x-sub :note="p.reply"/> + </div> + <div class="renote" v-if="isRenote"> + <p> + <router-link class="avatar-anchor" :to="`/@${acct}`"> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + </router-link> + %fa:retweet% + <span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> + <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> + <span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> + </p> + <mk-time :time="note.createdAt"/> + </div> + <article> + <router-link class="avatar-anchor" :to="`/@${pAcct}`"> + <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> + </router-link> + <div class="main"> + <header> + <router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link> + <span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span> + <span class="username">@{{ pAcct }}</span> + <div class="info"> + <span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> + <router-link class="created-at" :to="url"> + <mk-time :time="p.createdAt"/> + </router-link> + </div> + </header> + <div class="body"> + <p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p> + <div class="text"> + <a class="reply" v-if="p.reply"> + %fa:reply% + </a> + <mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> + <a class="rp" v-if="p.renote != null">RP:</a> + </div> + <div class="media" v-if="p.media.length > 0"> + <mk-media-list :media-list="p.media"/> + </div> + <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> + <div class="tags" v-if="p.tags && p.tags.length > 0"> + <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> + </div> + <mk-url-preview v-for="url in urls" :url="url" :key="url"/> + <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> + <div class="map" v-if="p.geo" ref="map"></div> + <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> + </div> + </div> + <footer> + <mk-reactions-viewer :note="p" ref="reactionsViewer"/> + <button @click="reply"> + %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> + </button> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> + </button> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton"> + %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> + </button> + <button class="menu" @click="menu" ref="menuButton"> + %fa:ellipsis-h% + </button> + </footer> + </div> + </article> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; +import parse from '../../../../../text/parse'; + +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; +import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; +import XSub from './note.sub.vue'; + +export default Vue.extend({ + components: { + XSub + }, + + props: ['note'], + + data() { + return { + connection: null, + connectionId: null + }; + }, + + computed: { + acct(): string { + return getAcct(this.note.user); + }, + name(): string { + return getUserName(this.note.user); + }, + pAcct(): string { + return getAcct(this.p.user); + }, + pName(): string { + return getUserName(this.p.user); + }, + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); + }, + p(): any { + return this.isRenote ? this.note.renote : this.note; + }, + reactionsCount(): number { + return this.p.reactionCounts + ? Object.keys(this.p.reactionCounts) + .map(key => this.p.reactionCounts[key]) + .reduce((a, b) => a + b) + : 0; + }, + url(): string { + return `/@${this.pAcct}/${this.p.id}`; + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } + } + }, + + created() { + if ((this as any).os.isSignedIn) { + this.connection = (this as any).os.stream.getConnection(); + this.connectionId = (this as any).os.stream.use(); + } + }, + + mounted() { + this.capture(true); + + if ((this as any).os.isSignedIn) { + this.connection.on('_connected_', this.onStreamConnected); + } + + // Draw map + if (this.p.geo) { + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true; + if (shouldShowMap) { + (this as any).os.getGoogleMaps().then(maps => { + const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); + const map = new maps.Map(this.$refs.map, { + center: uluru, + zoom: 15 + }); + new maps.Marker({ + position: uluru, + map: map + }); + }); + } + } + }, + + beforeDestroy() { + this.decapture(true); + + if ((this as any).os.isSignedIn) { + this.connection.off('_connected_', this.onStreamConnected); + (this as any).os.stream.dispose(this.connectionId); + } + }, + + methods: { + capture(withHandler = false) { + if ((this as any).os.isSignedIn) { + this.connection.send({ + type: 'capture', + id: this.p.id + }); + if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); + } + }, + decapture(withHandler = false) { + if ((this as any).os.isSignedIn) { + this.connection.send({ + type: 'decapture', + id: this.p.id + }); + if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); + } + }, + onStreamConnected() { + this.capture(); + }, + onStreamNoteUpdated(data) { + const note = data.note; + if (note.id == this.note.id) { + this.$emit('update:note', note); + } else if (note.id == this.note.renoteId) { + this.note.renote = note; + } + }, + reply() { + (this as any).apis.post({ + reply: this.p + }); + }, + renote() { + (this as any).apis.post({ + renote: this.p + }); + }, + react() { + (this as any).os.new(MkReactionPicker, { + source: this.$refs.reactButton, + note: this.p, + compact: true + }); + }, + menu() { + (this as any).os.new(MkNoteMenu, { + source: this.$refs.menuButton, + note: this.p, + compact: true + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.note + font-size 12px + border-bottom solid 1px #eaeaea + + &:first-child + border-radius 8px 8px 0 0 + + > .renote + border-radius 8px 8px 0 0 + + &:last-of-type + border-bottom none + + @media (min-width 350px) + font-size 14px + + @media (min-width 500px) + font-size 16px + + > .renote + color #9dbb00 + background linear-gradient(to bottom, #edfde2 0%, #fff 100%) + + > p + margin 0 + padding 8px 16px + line-height 28px + + @media (min-width 500px) + padding 16px + + .avatar-anchor + display inline-block + + .avatar + vertical-align bottom + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px + + [data-fa] + margin-right 4px + + .name + font-weight bold + + > .mk-time + position absolute + top 8px + right 16px + font-size 0.9em + line-height 28px + + @media (min-width 500px) + top 16px + + & + article + padding-top 8px + + > .reply-to + background rgba(0, 0, 0, 0.0125) + + > .mk-note-preview + background transparent + + > article + padding 14px 16px 9px 16px + + &:after + content "" + display block + clear both + + > .avatar-anchor + display block + float left + margin 0 10px 8px 0 + position -webkit-sticky + position sticky + top 62px + + @media (min-width 500px) + margin-right 16px + + > .avatar + display block + width 48px + height 48px + margin 0 + border-radius 6px + vertical-align bottom + + @media (min-width 500px) + width 58px + height 58px + border-radius 8px + + > .main + float left + width calc(100% - 58px) + + @media (min-width 500px) + width calc(100% - 74px) + + > header + display flex + align-items center + white-space nowrap + + @media (min-width 500px) + margin-bottom 2px + + > .name + display block + margin 0 0.5em 0 0 + padding 0 + overflow hidden + color #627079 + font-size 1em + font-weight bold + text-decoration none + text-overflow ellipsis + + &:hover + text-decoration underline + + > .is-bot + margin 0 0.5em 0 0 + padding 1px 6px + font-size 12px + color #aaa + border solid 1px #ddd + border-radius 3px + + > .username + margin 0 0.5em 0 0 + color #ccc + + > .info + margin-left auto + font-size 0.9em + + > .mobile + margin-right 6px + color #c0c0c0 + + > .created-at + color #c0c0c0 + + > .body + + > .text + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 1.1em + color #717171 + + >>> .quote + margin 8px + padding 6px 12px + color #aaa + border-left solid 3px #eee + + > .reply + margin-right 8px + color #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + [data-is-me]:after + content "you" + padding 0 4px + margin-left 4px + font-size 80% + color $theme-color-foreground + background $theme-color + border-radius 4px + + .mk-url-preview + margin-top 8px + + > .channel + margin 0 + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + > .media + > img + display block + max-width 100% + + > .location + margin 4px 0 + font-size 12px + color #ccc + + > .map + width 100% + height 200px + + &:empty + display none + + > .app + font-size 12px + color #ccc + + > .mk-poll + font-size 80% + + > .renote + margin 8px 0 + + > .mk-note-preview + padding 16px + border dashed 1px #c0dac6 + border-radius 8px + + > footer + > button + margin 0 + padding 8px + background transparent + border none + box-shadow none + font-size 1em + color #ddd + cursor pointer + + &:not(:last-child) + margin-right 28px + + &:hover + color #666 + + > .count + display inline + margin 0 0 0 8px + color #999 + + &.reacted + color $theme-color + + &.menu + @media (max-width 350px) + display none + +</style> + +<style lang="stylus" module> +.text + code + padding 4px 8px + margin 0 0.5em + font-size 80% + color #525252 + background #f8f8f8 + border-radius 2px + + pre > code + padding 16px + margin 0 +</style> diff --git a/src/client/app/mobile/views/components/posts.vue b/src/client/app/mobile/views/components/notes.vue index 4695f1beaa..573026d53e 100644 --- a/src/client/app/mobile/views/components/posts.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -1,12 +1,12 @@ <template> -<div class="mk-posts"> +<div class="mk-notes"> <slot name="head"></slot> <slot></slot> - <template v-for="(post, i) in _posts"> - <mk-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> - <p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> - <span>%fa:angle-up%{{ post._datetext }}</span> - <span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> + <template v-for="(note, i) in _notes"> + <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> + <p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> + <span>%fa:angle-up%{{ note._datetext }}</span> + <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> </p> </template> <footer> @@ -20,25 +20,25 @@ import Vue from 'vue'; export default Vue.extend({ props: { - posts: { + notes: { type: Array, default: () => [] } }, computed: { - _posts(): any[] { - return (this.posts as any).map(post => { - const date = new Date(post.createdAt).getDate(); - const month = new Date(post.createdAt).getMonth() + 1; - post._date = date; - post._datetext = `${month}月 ${date}日`; - return post; + _notes(): any[] { + return (this.notes as any).map(note => { + const date = new Date(note.createdAt).getDate(); + const month = new Date(note.createdAt).getMonth() + 1; + note._date = date; + note._datetext = `${month}月 ${date}日`; + return note; }); } }, methods: { - onPostUpdated(i, post) { - Vue.set((this as any).posts, i, post); + onNoteUpdated(i, note) { + Vue.set((this as any).notes, i, note); } } }); @@ -47,7 +47,7 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.mk-posts +.mk-notes background #fff border-radius 8px box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) diff --git a/src/client/app/mobile/views/components/notification-preview.vue b/src/client/app/mobile/views/components/notification-preview.vue index 0492c5d86c..79ca3321e4 100644 --- a/src/client/app/mobile/views/components/notification-preview.vue +++ b/src/client/app/mobile/views/components/notification-preview.vue @@ -4,23 +4,23 @@ <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> <p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p> - <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> + <p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> </div> </template> - <template v-if="notification.type == 'repost'"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <template v-if="notification.type == 'renote'"> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:retweet%{{ posterName }}</p> - <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p> + <p>%fa:retweet%{{ noteerName }}</p> + <p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p> </div> </template> <template v-if="notification.type == 'quote'"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:quote-left%{{ posterName }}</p> - <p class="post-preview">{{ getPostSummary(notification.post) }}</p> + <p>%fa:quote-left%{{ noteerName }}</p> + <p class="note-preview">{{ getNoteSummary(notification.note) }}</p> </div> </template> @@ -32,18 +32,18 @@ </template> <template v-if="notification.type == 'reply'"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:reply%{{ posterName }}</p> - <p class="post-preview">{{ getPostSummary(notification.post) }}</p> + <p>%fa:reply%{{ noteerName }}</p> + <p class="note-preview">{{ getNoteSummary(notification.note) }}</p> </div> </template> <template v-if="notification.type == 'mention'"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:at%{{ posterName }}</p> - <p class="post-preview">{{ getPostSummary(notification.post) }}</p> + <p>%fa:at%{{ noteerName }}</p> + <p class="note-preview">{{ getNoteSummary(notification.note) }}</p> </div> </template> @@ -51,7 +51,7 @@ <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> <p>%fa:chart-pie%{{ name }}</p> - <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> + <p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> </div> </template> </div> @@ -59,7 +59,7 @@ <script lang="ts"> import Vue from 'vue'; -import getPostSummary from '../../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../../renderers/get-note-summary'; import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ @@ -68,13 +68,13 @@ export default Vue.extend({ name() { return getUserName(this.notification.user); }, - posterName() { - return getUserName(this.notification.post.user); + noteerName() { + return getUserName(this.notification.note.user); } }, data() { return { - getPostSummary + getNoteSummary }; } }); @@ -112,7 +112,7 @@ export default Vue.extend({ i, mk-reaction-icon margin-right 4px - .post-ref + .note-ref [data-fa] font-size 1em @@ -121,7 +121,7 @@ export default Vue.extend({ display inline-block margin-right 3px - &.repost, &.quote + &.renote, &.quote .text p i color #77B255 diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue index 62a0e6425e..e1294a8778 100644 --- a/src/client/app/mobile/views/components/notification.vue +++ b/src/client/app/mobile/views/components/notification.vue @@ -10,31 +10,31 @@ <mk-reaction-icon :reaction="notification.reaction"/> <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> </p> - <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post) }} + <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note) }} %fa:quote-right% </router-link> </div> </div> - <div class="notification repost" v-if="notification.type == 'repost'"> + <div class="notification renote" v-if="notification.type == 'renote'"> <mk-time :time="notification.createdAt"/> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> </router-link> <div class="text"> <p> %fa:retweet% - <router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link> + <router-link :to="`/@${acct}`">{{ getUserName(notification.note.user) }}</router-link> </p> - <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% + <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% </router-link> </div> </div> <template v-if="notification.type == 'quote'"> - <mk-post :post="notification.post"/> + <mk-note :note="notification.note"/> </template> <div class="notification follow" v-if="notification.type == 'follow'"> @@ -51,11 +51,11 @@ </div> <template v-if="notification.type == 'reply'"> - <mk-post :post="notification.post"/> + <mk-note :note="notification.note"/> </template> <template v-if="notification.type == 'mention'"> - <mk-post :post="notification.post"/> + <mk-note :note="notification.note"/> </template> <div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> @@ -68,8 +68,8 @@ %fa:chart-pie% <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> </p> - <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> - %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% + <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> + %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% </router-link> </div> </div> @@ -78,7 +78,7 @@ <script lang="ts"> import Vue from 'vue'; -import getPostSummary from '../../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../../renderers/get-note-summary'; import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; @@ -91,13 +91,13 @@ export default Vue.extend({ name() { return getUserName(this.notification.user); }, - posterName() { - return getUserName(this.notification.post.user); + noteerName() { + return getUserName(this.notification.note.user); } }, data() { return { - getPostSummary + getNoteSummary }; } }); @@ -146,10 +146,10 @@ export default Vue.extend({ i, .mk-reaction-icon margin-right 4px - > .post-preview + > .note-preview color rgba(0, 0, 0, 0.7) - > .post-ref + > .note-ref color rgba(0, 0, 0, 0.7) [data-fa] @@ -159,7 +159,7 @@ export default Vue.extend({ display inline-block margin-right 3px - &.repost + &.renote .text p i color #77B255 diff --git a/src/client/app/mobile/views/components/post-detail.vue b/src/client/app/mobile/views/components/post-detail.vue index 0226ce081a..e1682e58ed 100644 --- a/src/client/app/mobile/views/components/post-detail.vue +++ b/src/client/app/mobile/views/components/post-detail.vue @@ -1,5 +1,5 @@ <template> -<div class="mk-post-detail"> +<div class="mk-note-detail"> <button class="more" v-if="p.reply && p.reply.replyId && context == null" @@ -10,21 +10,21 @@ <template v-if="contextFetching">%fa:spinner .pulse%</template> </button> <div class="context"> - <x-sub v-for="post in context" :key="post.id" :post="post"/> + <x-sub v-for="note in context" :key="note.id" :note="note"/> </div> <div class="reply-to" v-if="p.reply"> - <x-sub :post="p.reply"/> + <x-sub :note="p.reply"/> </div> - <div class="repost" v-if="isRepost"> + <div class="renote" v-if="isRenote"> <p> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> </router-link> %fa:retweet% <router-link class="name" :to="`/@${acct}`"> {{ name }} </router-link> - がRepost + がRenote </p> </div> <article> @@ -38,33 +38,33 @@ </div> </header> <div class="body"> - <mk-post-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/> + <mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/> <div class="tags" v-if="p.tags && p.tags.length > 0"> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> - <mk-poll v-if="p.poll" :post="p"/> + <mk-poll v-if="p.poll" :note="p"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> <div class="map" v-if="p.geo" ref="map"></div> - <div class="repost" v-if="p.repost"> - <mk-post-preview :post="p.repost"/> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> </div> </div> <router-link class="time" :to="`/@${pAcct}/${p.id}`"> <mk-time :time="p.createdAt" mode="detail"/> </router-link> <footer> - <mk-reactions-viewer :post="p"/> - <button @click="reply" title="%i18n:mobile.tags.mk-post-detail.reply%"> + <mk-reactions-viewer :note="p"/> + <button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%"> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> </button> - <button @click="repost" title="Repost"> - %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> </button> - <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%"> + <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%"> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> </button> <button @click="menu" ref="menuButton"> @@ -73,7 +73,7 @@ </footer> </article> <div class="replies" v-if="!compact"> - <x-sub v-for="post in replies" :key="post.id" :post="post"/> + <x-sub v-for="note in replies" :key="note.id" :note="note"/> </div> </div> </template> @@ -84,9 +84,9 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; -import MkPostMenu from '../../../common/views/components/post-menu.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; -import XSub from './post-detail.sub.vue'; +import XSub from './note-detail.sub.vue'; export default Vue.extend({ components: { @@ -94,7 +94,7 @@ export default Vue.extend({ }, props: { - post: { + note: { type: Object, required: true }, @@ -113,10 +113,10 @@ export default Vue.extend({ computed: { acct(): string { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name(): string { - return getUserName(this.post.user); + return getUserName(this.note.user); }, pAcct(): string { return getAcct(this.p.user); @@ -124,14 +124,14 @@ export default Vue.extend({ pName(): string { return getUserName(this.p.user); }, - isRepost(): boolean { - return (this.post.repost && - this.post.text == null && - this.post.mediaIds == null && - this.post.poll == null); + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); }, p(): any { - return this.isRepost ? this.post.repost : this.post; + return this.isRenote ? this.note.renote : this.note; }, reactionsCount(): number { return this.p.reactionCounts @@ -155,8 +155,8 @@ export default Vue.extend({ mounted() { // Get replies if (!this.compact) { - (this as any).api('posts/replies', { - postId: this.p.id, + (this as any).api('notes/replies', { + noteId: this.p.id, limit: 8 }).then(replies => { this.replies = replies; @@ -187,8 +187,8 @@ export default Vue.extend({ this.contextFetching = true; // Fetch context - (this as any).api('posts/context', { - postId: this.p.replyId + (this as any).api('notes/context', { + noteId: this.p.replyId }).then(context => { this.contextFetching = false; this.context = context.reverse(); @@ -199,22 +199,22 @@ export default Vue.extend({ reply: this.p }); }, - repost() { + renote() { (this as any).apis.post({ - repost: this.p + renote: this.p }); }, react() { (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, - post: this.p, + note: this.p, compact: true }); }, menu() { - (this as any).os.new(MkPostMenu, { + (this as any).os.new(MkNoteMenu, { source: this.$refs.menuButton, - post: this.p, + note: this.p, compact: true }); } @@ -225,7 +225,7 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.mk-post-detail +.mk-note-detail overflow hidden margin 0 auto padding 0 @@ -267,7 +267,7 @@ export default Vue.extend({ > * border-bottom 1px solid #eef0f2 - > .repost + > .renote color #9dbb00 background linear-gradient(to bottom, #edfde2 0%, #fff 100%) @@ -357,10 +357,10 @@ export default Vue.extend({ > .body padding 8px 0 - > .repost + > .renote margin 8px 0 - > .mk-post-preview + > .mk-note-preview padding 16px border dashed 1px #c0dac6 border-radius 8px diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 5b78a25710..6fcbbb47e8 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -9,8 +9,8 @@ </div> </header> <div class="form"> - <mk-post-preview v-if="reply" :post="reply"/> - <textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea> + <mk-note-preview v-if="reply" :note="reply"/> + <textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.note-placeholder%'"></textarea> <div class="attaches" v-show="files.length != 0"> <x-draggable class="files" :list="files" :options="{ animation: 150 }"> <div class="file" v-for="file in files" :key="file.id"> @@ -112,7 +112,7 @@ export default Vue.extend({ post() { this.posting = true; const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== true; - (this as any).api('posts/create', { + (this as any).api('notes/create', { text: this.text == '' ? undefined : this.text, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, @@ -127,7 +127,7 @@ export default Vue.extend({ } : null, viaMobile: viaMobile }).then(data => { - this.$emit('post'); + this.$emit('note'); this.$destroy(); }).catch(err => { this.posting = false; @@ -200,7 +200,7 @@ export default Vue.extend({ max-width 500px margin 0 auto - > .mk-post-preview + > .mk-note-preview padding 16px > .attaches diff --git a/src/client/app/mobile/views/components/post.vue b/src/client/app/mobile/views/components/post.vue index eee1e80fd3..4b33c6f071 100644 --- a/src/client/app/mobile/views/components/post.vue +++ b/src/client/app/mobile/views/components/post.vue @@ -1,19 +1,19 @@ <template> -<div class="post" :class="{ repost: isRepost }"> +<div class="note" :class="{ renote: isRenote }"> <div class="reply-to" v-if="p.reply"> - <x-sub :post="p.reply"/> + <x-sub :note="p.reply"/> </div> - <div class="repost" v-if="isRepost"> + <div class="renote" v-if="isRenote"> <p> <router-link class="avatar-anchor" :to="`/@${acct}`"> - <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> </router-link> %fa:retweet% - <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> + <span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> - <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> + <span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> </p> - <mk-time :time="post.createdAt"/> + <mk-time :time="note.createdAt"/> </div> <article> <router-link class="avatar-anchor" :to="`/@${pAcct}`"> @@ -37,13 +37,13 @@ <a class="reply" v-if="p.reply"> %fa:reply% </a> - <mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> - <a class="rp" v-if="p.repost != null">RP:</a> + <mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> + <a class="rp" v-if="p.renote != null">RP:</a> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> - <mk-poll v-if="p.poll" :post="p" ref="pollViewer"/> + <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> <div class="tags" v-if="p.tags && p.tags.length > 0"> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> </div> @@ -51,17 +51,17 @@ <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> <div class="map" v-if="p.geo" ref="map"></div> <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> - <div class="repost" v-if="p.repost"> - <mk-post-preview :post="p.repost"/> + <div class="renote" v-if="p.renote"> + <mk-note-preview :note="p.renote"/> </div> </div> <footer> - <mk-reactions-viewer :post="p" ref="reactionsViewer"/> + <mk-reactions-viewer :note="p" ref="reactionsViewer"/> <button @click="reply"> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> </button> - <button @click="repost" title="Repost"> - %fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> + <button @click="renote" title="Renote"> + %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> </button> <button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton"> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> @@ -81,16 +81,16 @@ import getAcct from '../../../../../acct/render'; import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; -import MkPostMenu from '../../../common/views/components/post-menu.vue'; +import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; -import XSub from './post.sub.vue'; +import XSub from './note.sub.vue'; export default Vue.extend({ components: { XSub }, - props: ['post'], + props: ['note'], data() { return { @@ -101,10 +101,10 @@ export default Vue.extend({ computed: { acct(): string { - return getAcct(this.post.user); + return getAcct(this.note.user); }, name(): string { - return getUserName(this.post.user); + return getUserName(this.note.user); }, pAcct(): string { return getAcct(this.p.user); @@ -112,14 +112,14 @@ export default Vue.extend({ pName(): string { return getUserName(this.p.user); }, - isRepost(): boolean { - return (this.post.repost && - this.post.text == null && - this.post.mediaIds == null && - this.post.poll == null); + isRenote(): boolean { + return (this.note.renote && + this.note.text == null && + this.note.mediaIds == null && + this.note.poll == null); }, p(): any { - return this.isRepost ? this.post.repost : this.post; + return this.isRenote ? this.note.renote : this.note; }, reactionsCount(): number { return this.p.reactionCounts @@ -192,7 +192,7 @@ export default Vue.extend({ type: 'capture', id: this.p.id }); - if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated); + if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); } }, decapture(withHandler = false) { @@ -201,18 +201,18 @@ export default Vue.extend({ type: 'decapture', id: this.p.id }); - if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); + if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); } }, onStreamConnected() { this.capture(); }, - onStreamPostUpdated(data) { - const post = data.post; - if (post.id == this.post.id) { - this.$emit('update:post', post); - } else if (post.id == this.post.repostId) { - this.post.repost = post; + onStreamNoteUpdated(data) { + const note = data.note; + if (note.id == this.note.id) { + this.$emit('update:note', note); + } else if (note.id == this.note.renoteId) { + this.note.renote = note; } }, reply() { @@ -220,22 +220,22 @@ export default Vue.extend({ reply: this.p }); }, - repost() { + renote() { (this as any).apis.post({ - repost: this.p + renote: this.p }); }, react() { (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, - post: this.p, + note: this.p, compact: true }); }, menu() { - (this as any).os.new(MkPostMenu, { + (this as any).os.new(MkNoteMenu, { source: this.$refs.menuButton, - post: this.p, + note: this.p, compact: true }); } @@ -246,14 +246,14 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.post +.note font-size 12px border-bottom solid 1px #eaeaea &:first-child border-radius 8px 8px 0 0 - > .repost + > .renote border-radius 8px 8px 0 0 &:last-of-type @@ -265,7 +265,7 @@ export default Vue.extend({ @media (min-width 500px) font-size 16px - > .repost + > .renote color #9dbb00 background linear-gradient(to bottom, #edfde2 0%, #fff 100%) @@ -309,7 +309,7 @@ export default Vue.extend({ > .reply-to background rgba(0, 0, 0, 0.0125) - > .mk-post-preview + > .mk-note-preview background transparent > article @@ -485,10 +485,10 @@ export default Vue.extend({ > .mk-poll font-size 80% - > .repost + > .renote margin 8px 0 - > .mk-post-preview + > .mk-note-preview padding 16px border dashed 1px #c0dac6 border-radius 8px diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue new file mode 100644 index 0000000000..22e6ebe3f1 --- /dev/null +++ b/src/client/app/mobile/views/components/sub-note-content.vue @@ -0,0 +1,43 @@ +<template> +<div class="mk-sub-note-content"> + <div class="body"> + <a class="reply" v-if="note.replyId">%fa:reply%</a> + <mk-note-html v-if="note.text" :text="note.text" :i="os.i"/> + <a class="rp" v-if="note.renoteId">RP: ...</a> + </div> + <details v-if="note.media.length > 0"> + <summary>({{ note.media.length }}個のメディア)</summary> + <mk-media-list :media-list="note.media"/> + </details> + <details v-if="note.poll"> + <summary>%i18n:mobile.tags.mk-sub-note-content.poll%</summary> + <mk-poll :note="note"/> + </details> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['note'] +}); +</script> + +<style lang="stylus" scoped> +.mk-sub-note-content + overflow-wrap break-word + + > .body + > .reply + margin-right 6px + color #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + mk-poll + font-size 80% + +</style> diff --git a/src/client/app/mobile/views/components/sub-post-content.vue b/src/client/app/mobile/views/components/sub-post-content.vue deleted file mode 100644 index 97dd987dd7..0000000000 --- a/src/client/app/mobile/views/components/sub-post-content.vue +++ /dev/null @@ -1,43 +0,0 @@ -<template> -<div class="mk-sub-post-content"> - <div class="body"> - <a class="reply" v-if="post.replyId">%fa:reply%</a> - <mk-post-html v-if="post.text" :text="post.text" :i="os.i"/> - <a class="rp" v-if="post.repostId">RP: ...</a> - </div> - <details v-if="post.media.length > 0"> - <summary>({{ post.media.length }}個のメディア)</summary> - <mk-media-list :media-list="post.media"/> - </details> - <details v-if="post.poll"> - <summary>%i18n:mobile.tags.mk-sub-post-content.poll%</summary> - <mk-poll :post="post"/> - </details> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; -export default Vue.extend({ - props: ['post'] -}); -</script> - -<style lang="stylus" scoped> -.mk-sub-post-content - overflow-wrap break-word - - > .body - > .reply - margin-right 6px - color #717171 - - > .rp - margin-left 4px - font-style oblique - color #a0bf46 - - mk-poll - font-size 80% - -</style> diff --git a/src/client/app/mobile/views/components/timeline.vue b/src/client/app/mobile/views/components/timeline.vue index 7b5948faf1..4d6abcd167 100644 --- a/src/client/app/mobile/views/components/timeline.vue +++ b/src/client/app/mobile/views/components/timeline.vue @@ -1,11 +1,11 @@ <template> <div class="mk-timeline"> <mk-friends-maker v-if="alone"/> - <mk-posts :posts="posts"> + <mk-notes :notes="notes"> <div class="init" v-if="fetching"> %fa:spinner .pulse%%i18n:common.loading% </div> - <div class="empty" v-if="!fetching && posts.length == 0"> + <div class="empty" v-if="!fetching && notes.length == 0"> %fa:R comments% %i18n:mobile.tags.mk-home-timeline.empty-timeline% </div> @@ -13,7 +13,7 @@ <span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span> <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> </button> - </mk-posts> + </mk-notes> </div> </template> @@ -33,7 +33,7 @@ export default Vue.extend({ return { fetching: true, moreFetching: false, - posts: [], + notes: [], existMore: false, connection: null, connectionId: null @@ -48,14 +48,14 @@ export default Vue.extend({ this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); - this.connection.on('post', this.onPost); + this.connection.on('note', this.onNote); this.connection.on('follow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing); this.fetch(); }, beforeDestroy() { - this.connection.off('post', this.onPost); + this.connection.off('note', this.onNote); this.connection.off('follow', this.onChangeFollowing); this.connection.off('unfollow', this.onChangeFollowing); (this as any).os.stream.dispose(this.connectionId); @@ -63,15 +63,15 @@ export default Vue.extend({ methods: { fetch(cb?) { this.fetching = true; - (this as any).api('posts/timeline', { + (this as any).api('notes/timeline', { limit: limit + 1, untilDate: this.date ? (this.date as any).getTime() : undefined - }).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } - this.posts = posts; + this.notes = notes; this.fetching = false; this.$emit('loaded'); if (cb) cb(); @@ -79,22 +79,22 @@ export default Vue.extend({ }, more() { this.moreFetching = true; - (this as any).api('posts/timeline', { + (this as any).api('notes/timeline', { limit: limit + 1, - untilId: this.posts[this.posts.length - 1].id - }).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + untilId: this.notes[this.notes.length - 1].id + }).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } else { this.existMore = false; } - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); this.moreFetching = false; }); }, - onPost(post) { - this.posts.unshift(post); + onNote(note) { + this.notes.unshift(note); }, onChangeFollowing() { this.fetch(); diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue index bd3e3d0c87..7a04441f76 100644 --- a/src/client/app/mobile/views/components/user-timeline.vue +++ b/src/client/app/mobile/views/components/user-timeline.vue @@ -1,18 +1,18 @@ <template> <div class="mk-user-timeline"> - <mk-posts :posts="posts"> + <mk-notes :notes="notes"> <div class="init" v-if="fetching"> %fa:spinner .pulse%%i18n:common.loading% </div> - <div class="empty" v-if="!fetching && posts.length == 0"> + <div class="empty" v-if="!fetching && notes.length == 0"> %fa:R comments% - {{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-posts-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-posts%' }} + {{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-notes-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-notes%' }} </div> <button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail"> <span v-if="!moreFetching">%i18n:mobile.tags.mk-user-timeline.load-more%</span> <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> </button> - </mk-posts> + </mk-notes> </div> </template> @@ -26,22 +26,22 @@ export default Vue.extend({ data() { return { fetching: true, - posts: [], + notes: [], existMore: false, moreFetching: false }; }, mounted() { - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, withMedia: this.withMedia, limit: limit + 1 - }).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } - this.posts = posts; + this.notes = notes; this.fetching = false; this.$emit('loaded'); }); @@ -49,19 +49,19 @@ export default Vue.extend({ methods: { more() { this.moreFetching = true; - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, withMedia: this.withMedia, limit: limit + 1, - untilId: this.posts[this.posts.length - 1].id - }).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + untilId: this.notes[this.notes.length - 1].id + }).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } else { this.existMore = false; } - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); this.moreFetching = false; }); } diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue index 6fb1c6d4fa..ab61166cf3 100644 --- a/src/client/app/mobile/views/pages/home.vue +++ b/src/client/app/mobile/views/pages/home.vue @@ -64,7 +64,7 @@ import Vue from 'vue'; import * as XDraggable from 'vuedraggable'; import * as uuid from 'uuid'; import Progress from '../../../common/scripts/loading'; -import getPostSummary from '../../../../../renderers/get-post-summary'; +import getNoteSummary from '../../../../../renderers/get-note-summary'; export default Vue.extend({ components: { @@ -124,14 +124,14 @@ export default Vue.extend({ this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); - this.connection.on('post', this.onStreamPost); + this.connection.on('note', this.onStreamNote); this.connection.on('mobile_home_updated', this.onHomeUpdated); document.addEventListener('visibilitychange', this.onVisibilitychange, false); Progress.start(); }, beforeDestroy() { - this.connection.off('post', this.onStreamPost); + this.connection.off('note', this.onStreamNote); this.connection.off('mobile_home_updated', this.onHomeUpdated); (this as any).os.stream.dispose(this.connectionId); document.removeEventListener('visibilitychange', this.onVisibilitychange); @@ -143,10 +143,10 @@ export default Vue.extend({ onLoaded() { Progress.done(); }, - onStreamPost(post) { - if (document.hidden && post.userId !== (this as any).os.i.id) { + onStreamNote(note) { + if (document.hidden && note.userId !== (this as any).os.i.id) { this.unreadCount++; - document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; + document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`; } }, onVisibilitychange() { diff --git a/src/client/app/mobile/views/pages/post.vue b/src/client/app/mobile/views/pages/note.vue index 49a4bfd9dc..89b8c776f2 100644 --- a/src/client/app/mobile/views/pages/post.vue +++ b/src/client/app/mobile/views/pages/note.vue @@ -1,12 +1,12 @@ <template> <mk-ui> - <span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-post-page.title%</span> + <span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-note-page.title%</span> <main v-if="!fetching"> - <a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:mobile.tags.mk-post-page.next%</a> + <a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:mobile.tags.mk-note-page.next%</a> <div> - <mk-post-detail :post="post"/> + <mk-note-detail :note="note"/> </div> - <a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:mobile.tags.mk-post-page.prev%</a> + <a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:mobile.tags.mk-note-page.prev%</a> </main> </mk-ui> </template> @@ -19,7 +19,7 @@ export default Vue.extend({ data() { return { fetching: true, - post: null + note: null }; }, watch: { @@ -37,10 +37,10 @@ export default Vue.extend({ Progress.start(); this.fetching = true; - (this as any).api('posts/show', { - postId: this.$route.params.post - }).then(post => { - this.post = post; + (this as any).api('notes/show', { + noteId: this.$route.params.note + }).then(note => { + this.note = note; this.fetching = false; Progress.done(); diff --git a/src/client/app/mobile/views/pages/search.vue b/src/client/app/mobile/views/pages/search.vue index cbab504e3c..a96832beec 100644 --- a/src/client/app/mobile/views/pages/search.vue +++ b/src/client/app/mobile/views/pages/search.vue @@ -2,13 +2,13 @@ <mk-ui> <span slot="header">%fa:search% {{ q }}</span> <main v-if="!fetching"> - <mk-posts :class="$style.posts" :posts="posts"> - <span v-if="posts.length == 0">{{ '%i18n:mobile.tags.mk-search-posts.empty%'.replace('{}', q) }}</span> + <mk-notes :class="$style.notes" :notes="notes"> + <span v-if="notes.length == 0">{{ '%i18n:mobile.tags.mk-search-notes.empty%'.replace('{}', q) }}</span> <button v-if="existMore" @click="more" :disabled="fetching" slot="tail"> <span v-if="!fetching">%i18n:mobile.tags.mk-timeline.load-more%</span> <span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span> </button> - </mk-posts> + </mk-notes> </main> </mk-ui> </template> @@ -25,7 +25,7 @@ export default Vue.extend({ return { fetching: true, existMore: false, - posts: [], + notes: [], offset: 0 }; }, @@ -48,30 +48,30 @@ export default Vue.extend({ this.fetching = true; Progress.start(); - (this as any).api('posts/search', Object.assign({ + (this as any).api('notes/search', Object.assign({ limit: limit + 1 - }, parse(this.q))).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }, parse(this.q))).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); this.existMore = true; } - this.posts = posts; + this.notes = notes; this.fetching = false; Progress.done(); }); }, more() { this.offset += limit; - (this as any).api('posts/search', Object.assign({ + (this as any).api('notes/search', Object.assign({ limit: limit + 1, offset: this.offset - }, parse(this.q))).then(posts => { - if (posts.length == limit + 1) { - posts.pop(); + }, parse(this.q))).then(notes => { + if (notes.length == limit + 1) { + notes.pop(); } else { this.existMore = false; } - this.posts = this.posts.concat(posts); + this.notes = this.notes.concat(notes); }); } } @@ -79,7 +79,7 @@ export default Vue.extend({ </script> <style lang="stylus" module> -.posts +.notes margin 8px auto max-width 500px width calc(100% - 16px) diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue index 3b7b4d6c28..08fa4e2774 100644 --- a/src/client/app/mobile/views/pages/user.vue +++ b/src/client/app/mobile/views/pages/user.vue @@ -27,8 +27,8 @@ </div> <div class="status"> <a> - <b>{{ user.postsCount | number }}</b> - <i>%i18n:mobile.tags.mk-user.posts%</i> + <b>{{ user.notesCount | number }}</b> + <i>%i18n:mobile.tags.mk-user.notes%</i> </a> <a :href="`@${acct}/following`"> <b>{{ user.followingCount | number }}</b> @@ -44,13 +44,13 @@ <nav> <div class="nav-container"> <a :data-is-active=" page == 'home' " @click="page = 'home'">%i18n:mobile.tags.mk-user.overview%</a> - <a :data-is-active=" page == 'posts' " @click="page = 'posts'">%i18n:mobile.tags.mk-user.timeline%</a> + <a :data-is-active=" page == 'notes' " @click="page = 'notes'">%i18n:mobile.tags.mk-user.timeline%</a> <a :data-is-active=" page == 'media' " @click="page = 'media'">%i18n:mobile.tags.mk-user.media%</a> </div> </nav> <div class="body"> <x-home v-if="page == 'home'" :user="user"/> - <mk-user-timeline v-if="page == 'posts'" :user="user"/> + <mk-user-timeline v-if="page == 'notes'" :user="user"/> <mk-user-timeline v-if="page == 'media'" :user="user" with-media/> </div> </main> diff --git a/src/client/app/mobile/views/pages/user/home.posts.vue b/src/client/app/mobile/views/pages/user/home.notes.vue index 654f7f63e0..02afed9b88 100644 --- a/src/client/app/mobile/views/pages/user/home.posts.vue +++ b/src/client/app/mobile/views/pages/user/home.notes.vue @@ -1,10 +1,10 @@ <template> -<div class="root posts"> - <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p> - <div v-if="!fetching && posts.length > 0"> - <mk-post-card v-for="post in posts" :key="post.id" :post="post"/> +<div class="root notes"> + <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-notes.loading%<mk-ellipsis/></p> + <div v-if="!fetching && notes.length > 0"> + <mk-note-card v-for="note in notes" :key="note.id" :note="note"/> </div> - <p class="empty" v-if="!fetching && posts.length == 0">%i18n:mobile.tags.mk-user-overview-posts.no-posts%</p> + <p class="empty" v-if="!fetching && notes.length == 0">%i18n:mobile.tags.mk-user-overview-notes.no-notes%</p> </div> </template> @@ -15,14 +15,14 @@ export default Vue.extend({ data() { return { fetching: true, - posts: [] + notes: [] }; }, mounted() { - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id - }).then(posts => { - this.posts = posts; + }).then(notes => { + this.notes = notes; this.fetching = false; }); } @@ -30,7 +30,7 @@ export default Vue.extend({ </script> <style lang="stylus" scoped> -.root.posts +.root.notes > div overflow-x scroll diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue index ecf5082072..1c59260812 100644 --- a/src/client/app/mobile/views/pages/user/home.photos.vue +++ b/src/client/app/mobile/views/pages/user/home.photos.vue @@ -5,7 +5,7 @@ <a v-for="image in images" class="img" :style="`background-image: url(${image.media.url}?thumbnail&size=256)`" - :href="`/@${getAcct(image.post.user)}/${image.post.id}`" + :href="`/@${getAcct(image.note.user)}/${image.note.id}`" ></a> </div> <p class="empty" v-if="!fetching && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p> @@ -28,15 +28,15 @@ export default Vue.extend({ getAcct }, mounted() { - (this as any).api('users/posts', { + (this as any).api('users/notes', { userId: this.user.id, withMedia: true, limit: 6 - }).then(posts => { - posts.forEach(post => { - post.media.forEach(media => { + }).then(notes => { + notes.forEach(note => { + note.media.forEach(media => { if (this.images.length < 9) this.images.push({ - post, + note, media }); }); diff --git a/src/client/app/mobile/views/pages/user/home.vue b/src/client/app/mobile/views/pages/user/home.vue index 1afcd1f5ba..2554084969 100644 --- a/src/client/app/mobile/views/pages/user/home.vue +++ b/src/client/app/mobile/views/pages/user/home.vue @@ -1,10 +1,10 @@ <template> <div class="root home"> - <mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/> - <section class="recent-posts"> - <h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> + <mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> + <section class="recent-notes"> + <h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-notes%</h2> <div> - <x-posts :user="user"/> + <x-notes :user="user"/> </div> </section> <section class="images"> @@ -37,14 +37,14 @@ <script lang="ts"> import Vue from 'vue'; -import XPosts from './home.posts.vue'; +import XNotes from './home.notes.vue'; import XPhotos from './home.photos.vue'; import XFriends from './home.friends.vue'; import XFollowersYouKnow from './home.followers-you-know.vue'; export default Vue.extend({ components: { - XPosts, + XNotes, XPhotos, XFriends, XFollowersYouKnow @@ -58,7 +58,7 @@ export default Vue.extend({ max-width 600px margin 0 auto - > .mk-post-detail + > .mk-note-detail margin 0 0 8px 0 > section diff --git a/src/client/app/stats/tags/index.tag b/src/client/app/stats/tags/index.tag index 63fdd24044..f8944c0832 100644 --- a/src/client/app/stats/tags/index.tag +++ b/src/client/app/stats/tags/index.tag @@ -2,7 +2,7 @@ <h1>Misskey<i>Statistics</i></h1> <main v-if="!initializing"> <mk-users stats={ stats }/> - <mk-posts stats={ stats }/> + <mk-notes stats={ stats }/> </main> <footer><a href={ _URL_ }>{ _HOST_ }</a></footer> <style lang="stylus" scoped> @@ -56,9 +56,9 @@ </script> </mk-index> -<mk-posts> - <h2>%i18n:stats.posts-count% <b>{ stats.postsCount }</b></h2> - <mk-posts-chart v-if="!initializing" data={ data }/> +<mk-notes> + <h2>%i18n:stats.notes-count% <b>{ stats.notesCount }</b></h2> + <mk-notes-chart v-if="!initializing" data={ data }/> <style lang="stylus" scoped> :scope display block @@ -70,7 +70,7 @@ this.stats = this.opts.stats; this.on('mount', () => { - this.$root.$data.os.api('aggregation/posts', { + this.$root.$data.os.api('aggregation/notes', { limit: 365 }).then(data => { this.update({ @@ -80,7 +80,7 @@ }); }); </script> -</mk-posts> +</mk-notes> <mk-users> <h2>%i18n:stats.users-count% <b>{ stats.usersCount }</b></h2> @@ -108,11 +108,11 @@ </script> </mk-users> -<mk-posts-chart> +<mk-notes-chart> <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> - <title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title> + <title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title> <polyline - riot-points={ pointsPost } + riot-points={ pointsNote } fill="none" stroke-width="1" stroke="#41ddde"/> @@ -122,7 +122,7 @@ stroke-width="1" stroke="#f7796c"/> <polyline - riot-points={ pointsRepost } + riot-points={ pointsRenote } fill="none" stroke-width="1" stroke="#a1de41"/> @@ -147,7 +147,7 @@ this.viewBoxY = 80; this.data = this.opts.data.reverse(); - this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); + this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); const peak = Math.max.apply(null, this.data.map(d => d.total)); this.on('mount', () => { @@ -156,14 +156,14 @@ this.render = () => { this.update({ - pointsPost: this.data.map((d, i) => `${i},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '), + pointsNote: this.data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '), pointsReply: this.data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '), - pointsRepost: this.data.map((d, i) => `${i},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '), + pointsRenote: this.data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '), pointsTotal: this.data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ') }); }; </script> -</mk-posts-chart> +</mk-notes-chart> <mk-users-chart> <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> diff --git a/src/client/docs/api/endpoints/posts/create.yaml b/src/client/docs/api/endpoints/notes/create.yaml index d2d6e27fc7..04ada2ecd5 100644 --- a/src/client/docs/api/endpoints/posts/create.yaml +++ b/src/client/docs/api/endpoints/notes/create.yaml @@ -1,8 +1,8 @@ -endpoint: "posts/create" +endpoint: "notes/create" desc: ja: "投稿します。" - en: "Compose new post." + en: "Compose new note." params: - name: "text" @@ -10,7 +10,7 @@ params: optional: true desc: ja: "投稿の本文" - en: "The text of your post" + en: "The text of your note" - name: "cw" type: "string" optional: true @@ -24,17 +24,17 @@ params: ja: "添付するメディア(1~4つ)" en: "Media you want to attach (1~4)" - name: "replyId" - type: "id(Post)" + type: "id(Note)" optional: true desc: ja: "返信する投稿" - en: "The post you want to reply" - - name: "repostId" - type: "id(Post)" + en: "The note you want to reply" + - name: "renoteId" + type: "id(Note)" optional: true desc: ja: "引用する投稿" - en: "The post you want to quote" + en: "The note you want to quote" - name: "poll" type: "object" optional: true @@ -51,9 +51,9 @@ params: en: "Choices of a poll" res: - - name: "createdPost" - type: "entity(Post)" + - name: "createdNote" + type: "entity(Note)" optional: false desc: ja: "作成した投稿" - en: "A post that created" + en: "A note that created" diff --git a/src/client/docs/api/endpoints/posts/timeline.yaml b/src/client/docs/api/endpoints/notes/timeline.yaml index 9c44dd736a..71c346f355 100644 --- a/src/client/docs/api/endpoints/posts/timeline.yaml +++ b/src/client/docs/api/endpoints/notes/timeline.yaml @@ -1,4 +1,4 @@ -endpoint: "posts/timeline" +endpoint: "notes/timeline" desc: ja: "タイムラインを取得します。" @@ -11,12 +11,12 @@ params: desc: ja: "取得する最大の数" - name: "sinceId" - type: "id(Post)" + type: "id(Note)" optional: true desc: ja: "指定すると、この投稿を基点としてより新しい投稿を取得します" - name: "untilId" - type: "id(Post)" + type: "id(Note)" optional: true desc: ja: "指定すると、この投稿を基点としてより古い投稿を取得します" diff --git a/src/client/docs/api/entities/note.yaml b/src/client/docs/api/entities/note.yaml new file mode 100644 index 0000000000..718d331d13 --- /dev/null +++ b/src/client/docs/api/entities/note.yaml @@ -0,0 +1,174 @@ +name: "Note" + +desc: + ja: "投稿。" + en: "A note." + +props: + - name: "id" + type: "id" + optional: false + desc: + ja: "投稿ID" + en: "The ID of this note" + - name: "createdAt" + type: "date" + optional: false + desc: + ja: "投稿日時" + en: "The posted date of this note" + - name: "viaMobile" + type: "boolean" + optional: true + desc: + ja: "モバイル端末から投稿したか否か(自己申告であることに留意)" + en: "Whether this note sent via a mobile device" + - name: "text" + type: "string" + optional: true + desc: + ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" + en: "The text of this note (in Markdown like format if local)" + - name: "textHtml" + type: "string" + optional: true + desc: + ja: "投稿の本文 (HTML) (投稿時は無視)" + en: "The text of this note (in HTML. Ignored when posting.)" + - name: "mediaIds" + type: "id(DriveFile)[]" + optional: true + desc: + ja: "添付されているメディアのID (なければレスポンスでは空配列)" + en: "The IDs of the attached media (empty array for response if no media is attached)" + - name: "media" + type: "entity(DriveFile)[]" + optional: true + desc: + ja: "添付されているメディア" + en: "The attached media" + - name: "userId" + type: "id(User)" + optional: false + desc: + ja: "投稿者ID" + en: "The ID of author of this note" + - name: "user" + type: "entity(User)" + optional: true + desc: + ja: "投稿者" + en: "The author of this note" + - name: "myReaction" + type: "string" + optional: true + desc: + ja: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>" + en: "The your <a href='/docs/api/reactions'>reaction</a> of this note" + - name: "reactionCounts" + type: "object" + optional: false + desc: + ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト" + - name: "replyId" + type: "id(Note)" + optional: true + desc: + ja: "返信した投稿のID" + en: "The ID of the replyed note" + - name: "reply" + type: "entity(Note)" + optional: true + desc: + ja: "返信した投稿" + en: "The replyed note" + - name: "renoteId" + type: "id(Note)" + optional: true + desc: + ja: "引用した投稿のID" + en: "The ID of the quoted note" + - name: "renote" + type: "entity(Note)" + optional: true + desc: + ja: "引用した投稿" + en: "The quoted note" + - name: "poll" + type: "object" + optional: true + desc: + ja: "投票" + en: "The poll" + defName: "poll" + def: + - name: "choices" + type: "object[]" + optional: false + desc: + ja: "投票の選択肢" + en: "The choices of this poll" + defName: "choice" + def: + - name: "id" + type: "number" + optional: false + desc: + ja: "選択肢ID" + en: "The ID of this choice" + - name: "isVoted" + type: "boolean" + optional: true + desc: + ja: "自分がこの選択肢に投票したかどうか" + en: "Whether you voted to this choice" + - name: "text" + type: "string" + optional: false + desc: + ja: "選択肢本文" + en: "The text of this choice" + - name: "votes" + type: "number" + optional: false + desc: + ja: "この選択肢に投票された数" + en: "The number voted for this choice" + - name: "geo" + type: "object" + optional: true + desc: + ja: "位置情報" + en: "Geo location" + defName: "geo" + def: + - name: "coordinates" + type: "number[]" + optional: false + desc: + ja: "座標。最初に経度:-180〜180で表す。最後に緯度:-90〜90で表す。" + - name: "altitude" + type: "number" + optional: false + desc: + ja: "高度。メートル単位で表す。" + - name: "accuracy" + type: "number" + optional: false + desc: + ja: "緯度、経度の精度。メートル単位で表す。" + - name: "altitudeAccuracy" + type: "number" + optional: false + desc: + ja: "高度の精度。メートル単位で表す。" + - name: "heading" + type: "number" + optional: false + desc: + ja: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。" + - name: "speed" + type: "number" + optional: false + desc: + ja: "速度。メートル / 秒数で表す。" diff --git a/src/client/docs/api/entities/post.yaml b/src/client/docs/api/entities/post.yaml index 7077700129..718d331d13 100644 --- a/src/client/docs/api/entities/post.yaml +++ b/src/client/docs/api/entities/post.yaml @@ -1,8 +1,8 @@ -name: "Post" +name: "Note" desc: ja: "投稿。" - en: "A post." + en: "A note." props: - name: "id" @@ -10,31 +10,31 @@ props: optional: false desc: ja: "投稿ID" - en: "The ID of this post" + en: "The ID of this note" - name: "createdAt" type: "date" optional: false desc: ja: "投稿日時" - en: "The posted date of this post" + en: "The posted date of this note" - name: "viaMobile" type: "boolean" optional: true desc: ja: "モバイル端末から投稿したか否か(自己申告であることに留意)" - en: "Whether this post sent via a mobile device" + en: "Whether this note sent via a mobile device" - name: "text" type: "string" optional: true desc: ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" - en: "The text of this post (in Markdown like format if local)" + en: "The text of this note (in Markdown like format if local)" - name: "textHtml" type: "string" optional: true desc: ja: "投稿の本文 (HTML) (投稿時は無視)" - en: "The text of this post (in HTML. Ignored when posting.)" + en: "The text of this note (in HTML. Ignored when posting.)" - name: "mediaIds" type: "id(DriveFile)[]" optional: true @@ -52,48 +52,48 @@ props: optional: false desc: ja: "投稿者ID" - en: "The ID of author of this post" + en: "The ID of author of this note" - name: "user" type: "entity(User)" optional: true desc: ja: "投稿者" - en: "The author of this post" + en: "The author of this note" - name: "myReaction" type: "string" optional: true desc: ja: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>" - en: "The your <a href='/docs/api/reactions'>reaction</a> of this post" + en: "The your <a href='/docs/api/reactions'>reaction</a> of this note" - name: "reactionCounts" type: "object" optional: false desc: ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト" - name: "replyId" - type: "id(Post)" + type: "id(Note)" optional: true desc: ja: "返信した投稿のID" - en: "The ID of the replyed post" + en: "The ID of the replyed note" - name: "reply" - type: "entity(Post)" + type: "entity(Note)" optional: true desc: ja: "返信した投稿" - en: "The replyed post" - - name: "repostId" - type: "id(Post)" + en: "The replyed note" + - name: "renoteId" + type: "id(Note)" optional: true desc: ja: "引用した投稿のID" - en: "The ID of the quoted post" - - name: "repost" - type: "entity(Post)" + en: "The ID of the quoted note" + - name: "renote" + type: "entity(Note)" optional: true desc: ja: "引用した投稿" - en: "The quoted post" + en: "The quoted note" - name: "poll" type: "object" optional: true diff --git a/src/client/docs/api/entities/user.yaml b/src/client/docs/api/entities/user.yaml index a1fae1482b..cccf42f221 100644 --- a/src/client/docs/api/entities/user.yaml +++ b/src/client/docs/api/entities/user.yaml @@ -81,24 +81,24 @@ props: desc: ja: "自分がこのユーザーをミュートしているか" en: "Whether you muted this user" - - name: "postsCount" + - name: "notesCount" type: "number" optional: false desc: ja: "投稿の数" - en: "The number of the posts of this user" - - name: "pinnedPost" - type: "entity(Post)" + en: "The number of the notes of this user" + - name: "pinnedNote" + type: "entity(Note)" optional: true desc: ja: "ピン留めされた投稿" - en: "The pinned post of this user" - - name: "pinnedPostId" - type: "id(Post)" + en: "The pinned note of this user" + - name: "pinnedNoteId" + type: "id(Note)" optional: true desc: ja: "ピン留めされた投稿のID" - en: "The ID of the pinned post of this user" + en: "The ID of the pinned note of this user" - name: "driveCapacity" type: "number" optional: false diff --git a/src/client/docs/mute.ja.pug b/src/client/docs/mute.ja.pug index 5e79af5f8c..807f7b67a7 100644 --- a/src/client/docs/mute.ja.pug +++ b/src/client/docs/mute.ja.pug @@ -4,7 +4,7 @@ p ユーザーページから、そのユーザーをミュートすることが p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります: ul - li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRepost) + li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote) li そのユーザーからの通知 li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴 diff --git a/src/client/docs/search.ja.pug b/src/client/docs/search.ja.pug index e14e8c867e..fc62d16cae 100644 --- a/src/client/docs/search.ja.pug +++ b/src/client/docs/search.ja.pug @@ -64,19 +64,19 @@ section tr td mute td - | mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostを除外する(デフォルト) + | mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteを除外する(デフォルト) br - | mute_related ... ミュートしているユーザーの投稿に対する返信やRepostだけ除外する + | mute_related ... ミュートしているユーザーの投稿に対する返信やRenoteだけ除外する br | mute_direct ... ミュートしているユーザーの投稿だけ除外する br - | disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostも含める + | disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteも含める br | direct_only ... ミュートしているユーザーの投稿だけに限定 br - | related_only ... ミュートしているユーザーの投稿に対する返信やRepostだけに限定 + | related_only ... ミュートしているユーザーの投稿に対する返信やRenoteだけに限定 br - | all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostに限定 + | all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteに限定 tr td reply td @@ -86,11 +86,11 @@ section br | null ... 特に限定しない(デフォルト) tr - td repost + td renote td - | true ... Repostに限定。 + | true ... Renoteに限定。 br - | false ... Repostでない投稿に限定。 + | false ... Renoteでない投稿に限定。 br | null ... 特に限定しない(デフォルト) tr |