Post --> Note

Closes #1411
This commit is contained in:
syuilo 2018-04-08 02:30:37 +09:00
parent c7106d250c
commit a1b490afa7
167 changed files with 4440 additions and 1762 deletions

View File

@ -33,7 +33,7 @@ common:
confused: "Confused" confused: "Confused"
pudding: "Pudding" pudding: "Pudding"
post_categories: note_categories:
music: "Music" music: "Music"
game: "Video Game" game: "Video Game"
anime: "Anime" anime: "Anime"
@ -124,7 +124,7 @@ common:
show-result: "Show result" show-result: "Show result"
voted: "Voted" voted: "Voted"
mk-post-menu: mk-note-menu:
pin: "Pin" pin: "Pin"
pinned: "Pinned" pinned: "Pinned"
select: "Select category" select: "Select category"
@ -211,7 +211,7 @@ ch:
textarea: "Write here" textarea: "Write here"
upload: "Upload" upload: "Upload"
drive: "Drive" drive: "Drive"
post: "Do" note: "Do"
posting: "Doing" posting: "Doing"
desktop: desktop:
@ -304,8 +304,8 @@ desktop:
settings: "Settings" settings: "Settings"
signout: "Sign out" signout: "Sign out"
mk-ui-header-post-button: mk-ui-header-note-button:
post: "Compose new Post" note: "Compose new Post"
mk-ui-header-notifications: mk-ui-header-notifications:
title: "Notifications" title: "Notifications"
@ -350,18 +350,18 @@ desktop:
no-users: "No muted users" no-users: "No muted users"
mk-post-form: mk-post-form:
post-placeholder: "What's happening?" note-placeholder: "What's happening?"
reply-placeholder: "Reply to this post..." reply-placeholder: "Reply to this note..."
quote-placeholder: "Quote this post..." quote-placeholder: "Quote this note..."
post: "Post" note: "Post"
reply: "Reply" reply: "Reply"
repost: "Repost" renote: "Renote"
posted: "Posted!" posted: "Posted!"
replied: "Replied!" replied: "Replied!"
reposted: "Reposted!" reposted: "Reposted!"
post-failed: "Failed to post" note-failed: "Failed to note"
reply-failed: "Failed to reply" reply-failed: "Failed to reply"
repost-failed: "Failed to repost" renote-failed: "Failed to renote"
posting: "Posting" posting: "Posting"
attach-media-from-local: "Attach media from your pc" attach-media-from-local: "Attach media from your pc"
attach-media-from-drive: "Attach media from the drive" attach-media-from-drive: "Attach media from the drive"
@ -371,14 +371,14 @@ desktop:
text-remain: "{} chars remaining" text-remain: "{} chars remaining"
mk-post-form-window: mk-post-form-window:
post: "New post" note: "New note"
reply: "Reply" reply: "Reply"
attaches: "{} media attached" attaches: "{} media attached"
uploading-media: "Uploading {} media" uploading-media: "Uploading {} media"
mk-post-page: mk-note-page:
prev: "Previous post" prev: "Previous note"
next: "Next post" next: "Next note"
mk-settings: mk-settings:
profile: "Profile" profile: "Profile"
@ -390,10 +390,10 @@ desktop:
other: "Other" other: "Other"
license: "License" license: "License"
mk-timeline-post: mk-timeline-note:
reposted-by: "Reposted by {}" reposted-by: "Reposted by {}"
reply: "Reply" reply: "Reply"
repost: "Repost" renote: "Renote"
add-reaction: "Add your reaction" add-reaction: "Add your reaction"
detail: "Show detail" detail: "Show detail"
@ -448,7 +448,7 @@ desktop:
mk-post-form-home-widget: mk-post-form-home-widget:
title: "Post" title: "Post"
post: "Post" note: "Post"
placeholder: "What's happening?" placeholder: "What's happening?"
mk-access-log-home-widget: mk-access-log-home-widget:
@ -463,16 +463,16 @@ desktop:
have-a-nice-day: "Have a nice day!" have-a-nice-day: "Have a nice day!"
next: "Next" next: "Next"
mk-repost-form: mk-renote-form:
quote: "Quote..." quote: "Quote..."
cancel: "Cancel" cancel: "Cancel"
repost: "Repost" renote: "Renote"
reposting: "Reposting..." reposting: "Reposting..."
success: "Reposted!" success: "Reposted!"
failure: "Failed to Repost" failure: "Failed to Renote"
mk-repost-form-window: mk-renote-form-window:
title: "Are you sure you want to repost this post?" title: "Are you sure you want to renote this note?"
mk-user: mk-user:
last-used-at: "Last used at" last-used-at: "Last used at"
@ -541,10 +541,10 @@ mobile:
notifications: "Notifications" notifications: "Notifications"
read-all: "Are you sure you want to mark all unread notifications as read?" read-all: "Are you sure you want to mark all unread notifications as read?"
mk-post-page: mk-note-page:
title: "Post" title: "Post"
prev: "Previous post" prev: "Previous note"
next: "Next post" next: "Next note"
mk-search-page: mk-search-page:
search: "Search" search: "Search"
@ -606,33 +606,33 @@ mobile:
unfollow: "Unfollow" unfollow: "Unfollow"
mk-home-timeline: mk-home-timeline:
empty-timeline: "There is no posts" empty-timeline: "There is no notes"
mk-notifications: mk-notifications:
more: "More" more: "More"
empty: "No notifications" empty: "No notifications"
mk-post-detail: mk-note-detail:
reply: "Reply" reply: "Reply"
reaction: "Reaction" reaction: "Reaction"
mk-post-form: mk-post-form:
submit: "Post" submit: "Post"
reply-placeholder: "Reply to this post..." reply-placeholder: "Reply to this note..."
post-placeholder: "What's happening?" note-placeholder: "What's happening?"
mk-search-posts: mk-search-notes:
empty: "There is no post related to the 「{}」" empty: "There is no note related to the 「{}」"
mk-sub-post-content: mk-sub-note-content:
media-count: "{} media" media-count: "{} media"
poll: "Poll" poll: "Poll"
mk-timeline-post: mk-timeline-note:
reposted-by: "Reposted by {}" reposted-by: "Reposted by {}"
mk-timeline: mk-timeline:
empty: "No posts" empty: "No notes"
load-more: "More" load-more: "More"
mk-ui-nav: mk-ui-nav:
@ -652,21 +652,21 @@ mobile:
no-users: "No following." no-users: "No following."
mk-user-timeline: mk-user-timeline:
no-posts: "This user seems never post" no-notes: "This user seems never note"
no-posts-with-media: "There is no posts with media" no-notes-with-media: "There is no notes with media"
load-more: "More" load-more: "More"
mk-user: mk-user:
follows-you: "Follows you" follows-you: "Follows you"
following: "Following" following: "Following"
followers: "Followers" followers: "Followers"
posts: "Posts" notes: "Posts"
overview: "Overview" overview: "Overview"
timeline: "Timeline" timeline: "Timeline"
media: "Media" media: "Media"
mk-user-overview: mk-user-overview:
recent-posts: "Recent posts" recent-notes: "Recent notes"
images: "Images" images: "Images"
activity: "Activity" activity: "Activity"
keywords: "Keywords" keywords: "Keywords"
@ -675,9 +675,9 @@ mobile:
followers-you-know: "Followers you know" followers-you-know: "Followers you know"
last-used-at: "Last used at" last-used-at: "Last used at"
mk-user-overview-posts: mk-user-overview-notes:
loading: "Loading" loading: "Loading"
no-posts: "No posts" no-notes: "No notes"
mk-user-overview-photos: mk-user-overview-photos:
loading: "Loading" loading: "Loading"
@ -703,7 +703,7 @@ mobile:
load-more: "More" load-more: "More"
stats: stats:
posts-count: "Number of all posts" notes-count: "Number of all notes"
users-count: "Number of all users" users-count: "Number of all users"
status: status:

View File

@ -33,7 +33,7 @@ common:
confused: "こまこまのこまり" confused: "こまこまのこまり"
pudding: "Pudding" pudding: "Pudding"
post_categories: note_categories:
music: "音楽" music: "音楽"
game: "ゲーム" game: "ゲーム"
anime: "アニメ" anime: "アニメ"
@ -124,7 +124,7 @@ common:
show-result: "結果を見る" show-result: "結果を見る"
voted: "投票済み" voted: "投票済み"
mk-post-menu: mk-note-menu:
pin: "ピン留め" pin: "ピン留め"
pinned: "ピン留めしました" pinned: "ピン留めしました"
select: "カテゴリを選択" select: "カテゴリを選択"
@ -211,7 +211,7 @@ ch:
textarea: "書いて" textarea: "書いて"
upload: "アップロード" upload: "アップロード"
drive: "ドライブ" drive: "ドライブ"
post: "やる" note: "やる"
posting: "やってます" posting: "やってます"
desktop: desktop:
@ -304,8 +304,8 @@ desktop:
settings: "設定" settings: "設定"
signout: "サインアウト" signout: "サインアウト"
mk-ui-header-post-button: mk-ui-header-note-button:
post: "新規投稿" note: "新規投稿"
mk-ui-header-notifications: mk-ui-header-notifications:
title: "通知" title: "通知"
@ -350,18 +350,18 @@ desktop:
no-users: "ミュートしているユーザーはいません" no-users: "ミュートしているユーザーはいません"
mk-post-form: mk-post-form:
post-placeholder: "いまどうしてる?" note-placeholder: "いまどうしてる?"
reply-placeholder: "この投稿への返信..." reply-placeholder: "この投稿への返信..."
quote-placeholder: "この投稿を引用..." quote-placeholder: "この投稿を引用..."
post: "投稿" note: "投稿"
reply: "返信" reply: "返信"
repost: "Repost" renote: "Renote"
posted: "投稿しました!" posted: "投稿しました!"
replied: "返信しました!" replied: "返信しました!"
reposted: "Repostしました!" reposted: "Renoteしました!"
post-failed: "投稿に失敗しました" note-failed: "投稿に失敗しました"
reply-failed: "返信に失敗しました" reply-failed: "返信に失敗しました"
repost-failed: "Repostに失敗しました" renote-failed: "Renoteに失敗しました"
posting: "投稿中" posting: "投稿中"
attach-media-from-local: "PCからメディアを添付" attach-media-from-local: "PCからメディアを添付"
attach-media-from-drive: "ドライブからメディアを添付" attach-media-from-drive: "ドライブからメディアを添付"
@ -371,12 +371,12 @@ desktop:
text-remain: "のこり{}文字" text-remain: "のこり{}文字"
mk-post-form-window: mk-post-form-window:
post: "新規投稿" note: "新規投稿"
reply: "返信" reply: "返信"
attaches: "添付: {}メディア" attaches: "添付: {}メディア"
uploading-media: "{}個のメディアをアップロード中" uploading-media: "{}個のメディアをアップロード中"
mk-post-page: mk-note-page:
prev: "前の投稿" prev: "前の投稿"
next: "次の投稿" next: "次の投稿"
@ -390,10 +390,10 @@ desktop:
other: "その他" other: "その他"
license: "ライセンス" license: "ライセンス"
mk-timeline-post: mk-timeline-note:
reposted-by: "{}がRepost" reposted-by: "{}がRenote"
reply: "返信" reply: "返信"
repost: "Repost" renote: "Renote"
add-reaction: "リアクション" add-reaction: "リアクション"
detail: "詳細" detail: "詳細"
@ -448,7 +448,7 @@ desktop:
mk-post-form-home-widget: mk-post-form-home-widget:
title: "投稿" title: "投稿"
post: "投稿" note: "投稿"
placeholder: "いまどうしてる?" placeholder: "いまどうしてる?"
mk-access-log-home-widget: mk-access-log-home-widget:
@ -463,16 +463,16 @@ desktop:
have-a-nice-day: "良い一日を!" have-a-nice-day: "良い一日を!"
next: "次" next: "次"
mk-repost-form: mk-renote-form:
quote: "引用する..." quote: "引用する..."
cancel: "キャンセル" cancel: "キャンセル"
repost: "Repost" renote: "Renote"
reposting: "しています..." reposting: "しています..."
success: "Repostしました!" success: "Renoteしました!"
failure: "Repostに失敗しました" failure: "Renoteに失敗しました"
mk-repost-form-window: mk-renote-form-window:
title: "この投稿をRepostしますか?" title: "この投稿をRenoteしますか?"
mk-user: mk-user:
last-used-at: "最終アクセス" last-used-at: "最終アクセス"
@ -541,7 +541,7 @@ mobile:
notifications: "通知" notifications: "通知"
read-all: "すべての通知を既読にしますか?" read-all: "すべての通知を既読にしますか?"
mk-post-page: mk-note-page:
title: "投稿" title: "投稿"
prev: "前の投稿" prev: "前の投稿"
next: "次の投稿" next: "次の投稿"
@ -612,24 +612,24 @@ mobile:
more: "もっと見る" more: "もっと見る"
empty: "ありません!" empty: "ありません!"
mk-post-detail: mk-note-detail:
reply: "返信" reply: "返信"
reaction: "リアクション" reaction: "リアクション"
mk-post-form: mk-post-form:
submit: "投稿" submit: "投稿"
reply-placeholder: "この投稿への返信..." reply-placeholder: "この投稿への返信..."
post-placeholder: "いまどうしてる?" note-placeholder: "いまどうしてる?"
mk-search-posts: mk-search-notes:
empty: "「{}」に関する投稿は見つかりませんでした。" empty: "「{}」に関する投稿は見つかりませんでした。"
mk-sub-post-content: mk-sub-note-content:
media-count: "{}個のメディア" media-count: "{}個のメディア"
poll: "投票" poll: "投票"
mk-timeline-post: mk-timeline-note:
reposted-by: "{}がRepost" reposted-by: "{}がRenote"
mk-timeline: mk-timeline:
empty: "表示するものがありません" empty: "表示するものがありません"
@ -652,21 +652,21 @@ mobile:
no-users: "フォロー中のユーザーはいないようです。" no-users: "フォロー中のユーザーはいないようです。"
mk-user-timeline: mk-user-timeline:
no-posts: "このユーザーはまだ投稿していないようです。" no-notes: "このユーザーはまだ投稿していないようです。"
no-posts-with-media: "メディア付き投稿はありません。" no-notes-with-media: "メディア付き投稿はありません。"
load-more: "もっとみる" load-more: "もっとみる"
mk-user: mk-user:
follows-you: "フォローされています" follows-you: "フォローされています"
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
posts: "投稿" notes: "投稿"
overview: "概要" overview: "概要"
timeline: "タイムライン" timeline: "タイムライン"
media: "メディア" media: "メディア"
mk-user-overview: mk-user-overview:
recent-posts: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
@ -675,9 +675,9 @@ mobile:
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mk-user-overview-posts: mk-user-overview-notes:
loading: "読み込み中" loading: "読み込み中"
no-posts: "投稿はありません" no-notes: "投稿はありません"
mk-user-overview-photos: mk-user-overview-photos:
loading: "読み込み中" loading: "読み込み中"
@ -703,7 +703,7 @@ mobile:
load-more: "もっと" load-more: "もっと"
stats: stats:
posts-count: "投稿の数" notes-count: "投稿の数"
users-count: "アカウントの数" users-count: "アカウントの数"
status: status:

View File

@ -16,7 +16,7 @@
<template v-for="p in app.permission"> <template v-for="p in app.permission">
<li v-if="p == 'account-read'">アカウントの情報を見る</li> <li v-if="p == 'account-read'">アカウントの情報を見る</li>
<li v-if="p == 'account-write'">アカウントの情報を操作する</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 == 'like-write'">いいねしたりいいね解除する</li>
<li v-if="p == 'following-write'">フォローしたりフォロー解除する</li> <li v-if="p == 'following-write'">フォローしたりフォロー解除する</li>
<li v-if="p == 'drive-read'">ドライブを見る</li> <li v-if="p == 'drive-read'">ドライブを見る</li>

View File

@ -15,11 +15,11 @@
</div> </div>
<div class="body"> <div class="body">
<p v-if="postsFetching">読み込み中<mk-ellipsis/></p> <p v-if="notesFetching">読み込み中<mk-ellipsis/></p>
<div v-if="!postsFetching"> <div v-if="!notesFetching">
<p v-if="posts == null || posts.length == 0">まだ投稿がありません</p> <p v-if="notes == null || notes.length == 0">まだ投稿がありません</p>
<template v-if="posts != null"> <template v-if="notes != null">
<mk-channel-post each={ post in posts.slice().reverse() } post={ post } form={ parent.refs.form }/> <mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/>
</template> </template>
</div> </div>
</div> </div>
@ -62,9 +62,9 @@
this.id = this.opts.id; this.id = this.opts.id;
this.fetching = true; this.fetching = true;
this.postsFetching = true; this.notesFetching = true;
this.channel = null; this.channel = null;
this.posts = null; this.notes = null;
this.connection = new ChannelStream(this.id); this.connection = new ChannelStream(this.id);
this.unreadCount = 0; this.unreadCount = 0;
@ -95,9 +95,9 @@
}); });
// 投稿読み込み // 投稿読み込み
this.$root.$data.os.api('channels/posts', { this.$root.$data.os.api('channels/notes', {
channelId: this.id channelId: this.id
}).then(posts => { }).then(notes => {
if (fetched) { if (fetched) {
Progress.done(); Progress.done();
} else { } else {
@ -106,26 +106,26 @@
} }
this.update({ this.update({
postsFetching: false, notesFetching: false,
posts: posts notes: notes
}); });
}); });
this.connection.on('post', this.onPost); this.connection.on('note', this.onNote);
document.addEventListener('visibilitychange', this.onVisibilitychange, false); document.addEventListener('visibilitychange', this.onVisibilitychange, false);
}); });
this.on('unmount', () => { this.on('unmount', () => {
this.connection.off('post', this.onPost); this.connection.off('note', this.onNote);
this.connection.close(); this.connection.close();
document.removeEventListener('visibilitychange', this.onVisibilitychange); document.removeEventListener('visibilitychange', this.onVisibilitychange);
}); });
this.onPost = post => { this.onNote = note => {
this.posts.unshift(post); this.notes.unshift(note);
this.update(); 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++; this.unreadCount++;
document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`; document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`;
} }
@ -162,19 +162,19 @@
</script> </script>
</mk-channel> </mk-channel>
<mk-channel-post> <mk-channel-note>
<header> <header>
<a class="index" @click="reply">{ post.index }:</a> <a class="index" @click="reply">{ note.index }:</a>
<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(post.user) }</b></a> <a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a>
<mk-time time={ post.createdAt }/> <mk-time time={ note.createdAt }/>
<mk-time time={ post.createdAt } mode="detail"/> <mk-time time={ note.createdAt } mode="detail"/>
<span>ID:<i>{ acct }</i></span> <span>ID:<i>{ acct }</i></span>
</header> </header>
<div> <div>
<a v-if="post.reply">&gt;&gt;{ post.reply.index }</a> <a v-if="note.reply">&gt;&gt;{ note.reply.index }</a>
{ post.text } { note.text }
<div class="media" v-if="post.media"> <div class="media" v-if="note.media">
<template each={ file in post.media }> <template each={ file in note.media }>
<a href={ file.url } target="_blank"> <a href={ file.url } target="_blank">
<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/> <img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
</a> </a>
@ -232,18 +232,18 @@
import getAcct from '../../../../acct/render'; import getAcct from '../../../../acct/render';
import getUserName from '../../../../renderers/get-user-name'; import getUserName from '../../../../renderers/get-user-name';
this.post = this.opts.post; this.note = this.opts.note;
this.form = this.opts.form; this.form = this.opts.form;
this.acct = getAcct(this.post.user); this.acct = getAcct(this.note.user);
this.name = getUserName(this.post.user); this.name = getUserName(this.note.user);
this.reply = () => { this.reply = () => {
this.form.update({ this.form.update({
reply: this.post reply: this.note
}); });
}; };
</script> </script>
</mk-channel-post> </mk-channel-note>
<mk-channel-form> <mk-channel-form>
<p v-if="reply"><b>&gt;&gt;{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p> <p v-if="reply"><b>&gt;&gt;{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p>
@ -251,8 +251,8 @@
<div class="actions"> <div class="actions">
<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button> <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 @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"> <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.post%' }<mk-ellipsis v-if="wait"/> <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> </button>
</div> </div>
<mk-uploader ref="uploader"/> <mk-uploader ref="uploader"/>
@ -321,7 +321,7 @@
this.$refs.text.value = ''; this.$refs.text.value = '';
}; };
this.post = () => { this.note = () => {
this.update({ this.update({
wait: true wait: true
}); });
@ -330,7 +330,7 @@
? this.files.map(f => f.id) ? this.files.map(f => f.id)
: undefined; : 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, text: this.$refs.text.value == '' ? undefined : this.$refs.text.value,
mediaIds: files, mediaIds: files,
replyId: this.reply ? this.reply.id : undefined, replyId: this.reply ? this.reply.id : undefined,

View File

@ -49,7 +49,7 @@ export type API = {
post: (opts?: { post: (opts?: {
reply?: any; reply?: any;
repost?: any; renote?: any;
}) => void; }) => void;
notify: (message: string) => void; notify: (message: string) => void;
@ -312,7 +312,7 @@ export default class MiOS extends EventEmitter {
// Finish init // Finish init
callback(); callback();
//#region Post //#region Note
// Init service worker // Init service worker
if (this.shouldRegisterSw) this.registerSw(); if (this.shouldRegisterSw) this.registerSw();

View File

@ -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 getReactionEmoji from '../../../../renderers/get-reaction-emoji';
import getUserName from '../../../../renderers/get-user-name'; import getUserName from '../../../../renderers/get-user-name';
@ -23,28 +23,28 @@ export default function(type, data): Notification {
case 'mention': case 'mention':
return { return {
title: `${getUserName(data.user)}さんから:`, title: `${getUserName(data.user)}さんから:`,
body: getPostSummary(data), body: getNoteSummary(data),
icon: data.user.avatarUrl + '?thumbnail&size=64' icon: data.user.avatarUrl + '?thumbnail&size=64'
}; };
case 'reply': case 'reply':
return { return {
title: `${getUserName(data.user)}さんから返信:`, title: `${getUserName(data.user)}さんから返信:`,
body: getPostSummary(data), body: getNoteSummary(data),
icon: data.user.avatarUrl + '?thumbnail&size=64' icon: data.user.avatarUrl + '?thumbnail&size=64'
}; };
case 'quote': case 'quote':
return { return {
title: `${getUserName(data.user)}さんが引用:`, title: `${getUserName(data.user)}さんが引用:`,
body: getPostSummary(data), body: getNoteSummary(data),
icon: data.user.avatarUrl + '?thumbnail&size=64' icon: data.user.avatarUrl + '?thumbnail&size=64'
}; };
case 'reaction': case 'reaction':
return { return {
title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
body: getPostSummary(data.post), body: getNoteSummary(data.note),
icon: data.user.avatarUrl + '?thumbnail&size=64' icon: data.user.avatarUrl + '?thumbnail&size=64'
}; };

View File

@ -19,8 +19,8 @@ export default function(qs: string) {
case 'reply': case 'reply':
q['reply'] = value == 'null' ? null : value == 'true'; q['reply'] = value == 'null' ? null : value == 'true';
break; break;
case 'repost': case 'renote':
q['repost'] = value == 'null' ? null : value == 'true'; q['renote'] = value == 'null' ? null : value == 'true';
break; break;
case 'media': case 'media':
q['media'] = value == 'null' ? null : value == 'true'; q['media'] = value == 'null' ? null : value == 'true';

View File

@ -4,7 +4,7 @@ import signin from './signin.vue';
import signup from './signup.vue'; import signup from './signup.vue';
import forkit from './forkit.vue'; import forkit from './forkit.vue';
import nav from './nav.vue'; import nav from './nav.vue';
import postHtml from './post-html'; import noteHtml from './note-html';
import poll from './poll.vue'; import poll from './poll.vue';
import pollEditor from './poll-editor.vue'; import pollEditor from './poll-editor.vue';
import reactionIcon from './reaction-icon.vue'; import reactionIcon from './reaction-icon.vue';
@ -29,7 +29,7 @@ Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup); Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit); Vue.component('mk-forkit', forkit);
Vue.component('mk-nav', nav); 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', poll);
Vue.component('mk-poll-editor', pollEditor); Vue.component('mk-poll-editor', pollEditor);
Vue.component('mk-reaction-icon', reactionIcon); Vue.component('mk-reaction-icon', reactionIcon);

View File

@ -10,7 +10,7 @@
<img src="/assets/desktop/messaging/delete.png" alt="Delete"/> <img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
</button> </button>
<div class="content" v-if="!message.isDeleted"> <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"> <div class="file" v-if="message.file">
<a :href="message.file.url" target="_blank" :title="message.file.name"> <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"/> <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>

View File

@ -9,7 +9,7 @@ const flatten = list => list.reduce(
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] (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: { props: {
text: { text: {
type: String, type: String,

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="mk-post-menu"> <div class="mk-note-menu">
<div class="backdrop" ref="backdrop" @click="close"></div> <div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact }" ref="popover"> <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>
</div> </div>
</template> </template>
@ -12,7 +12,7 @@ import Vue from 'vue';
import * as anime from 'animejs'; import * as anime from 'animejs';
export default Vue.extend({ export default Vue.extend({
props: ['post', 'source', 'compact'], props: ['note', 'source', 'compact'],
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
const popover = this.$refs.popover as any; const popover = this.$refs.popover as any;
@ -51,7 +51,7 @@ export default Vue.extend({
methods: { methods: {
pin() { pin() {
(this as any).api('i/pin', { (this as any).api('i/pin', {
postId: this.post.id noteId: this.note.id
}).then(() => { }).then(() => {
this.$destroy(); this.$destroy();
}); });
@ -83,7 +83,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
$border-color = rgba(27, 31, 35, 0.15) $border-color = rgba(27, 31, 35, 0.15)
.mk-post-menu .mk-note-menu
position initial position initial
> .backdrop > .backdrop

View File

@ -22,7 +22,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
data() { data() {
return { return {
showResult: false showResult: false
@ -30,7 +30,7 @@ export default Vue.extend({
}, },
computed: { computed: {
poll(): any { poll(): any {
return this.post.poll; return this.note.poll;
}, },
total(): number { total(): number {
return this.poll.choices.reduce((a, b) => a + b.votes, 0); return this.poll.choices.reduce((a, b) => a + b.votes, 0);
@ -48,8 +48,8 @@ export default Vue.extend({
}, },
vote(id) { vote(id) {
if (this.poll.choices.some(c => c.isVoted)) return; if (this.poll.choices.some(c => c.isVoted)) return;
(this as any).api('posts/polls/vote', { (this as any).api('notes/polls/vote', {
postId: this.post.id, noteId: this.note.id,
choice: id choice: id
}).then(() => { }).then(() => {
this.poll.choices.forEach(c => { this.poll.choices.forEach(c => {

View File

@ -25,7 +25,7 @@ import * as anime from 'animejs';
const placeholder = '%i18n:common.tags.mk-reaction-picker.choose-reaction%'; const placeholder = '%i18n:common.tags.mk-reaction-picker.choose-reaction%';
export default Vue.extend({ export default Vue.extend({
props: ['post', 'source', 'compact', 'cb'], props: ['note', 'source', 'compact', 'cb'],
data() { data() {
return { return {
title: placeholder title: placeholder
@ -68,8 +68,8 @@ export default Vue.extend({
}, },
methods: { methods: {
react(reaction) { react(reaction) {
(this as any).api('posts/reactions/create', { (this as any).api('notes/reactions/create', {
postId: this.post.id, noteId: this.note.id,
reaction: reaction reaction: reaction
}).then(() => { }).then(() => {
if (this.cb) this.cb(); if (this.cb) this.cb();

View File

@ -17,10 +17,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
reactions(): number { reactions(): number {
return this.post.reactionCounts; return this.note.reactionCounts;
} }
} }
}); });

View File

@ -1,21 +1,21 @@
<template> <template>
<div class="mk-welcome-timeline"> <div class="mk-welcome-timeline">
<div v-for="post in posts"> <div v-for="note in notes">
<router-link class="avatar-anchor" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id"> <router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id">
<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> </router-link>
<div class="body"> <div class="body">
<header> <header>
<router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ getUserName(post.user) }}</router-link> <router-link class="name" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id">{{ getUserName(note.user) }}</router-link>
<span class="username">@{{ getAcct(post.user) }}</span> <span class="username">@{{ getAcct(note.user) }}</span>
<div class="info"> <div class="info">
<router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`"> <router-link class="created-at" :to="`/@${getAcct(note.user)}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</div> </div>
</header> </header>
<div class="text"> <div class="text">
<mk-post-html :text="post.text"/> <mk-note-html :text="note.text"/>
</div> </div>
</div> </div>
</div> </div>
@ -31,7 +31,7 @@ export default Vue.extend({
data() { data() {
return { return {
fetching: true, fetching: true,
posts: [] notes: []
}; };
}, },
mounted() { mounted() {
@ -42,14 +42,14 @@ export default Vue.extend({
getUserName, getUserName,
fetch(cb?) { fetch(cb?) {
this.fetching = true; this.fetching = true;
(this as any).api('posts', { (this as any).api('notes', {
reply: false, reply: false,
repost: false, renote: false,
media: false, media: false,
poll: false, poll: false,
bot: false bot: false
}).then(posts => { }).then(notes => {
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
}); });
} }

View File

@ -1,12 +1,12 @@
import PostFormWindow from '../views/components/post-form-window.vue'; 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) { export default function(opts) {
const o = opts || {}; const o = opts || {};
if (o.repost) { if (o.renote) {
const vm = new RepostFormWindow({ const vm = new RenoteFormWindow({
propsData: { propsData: {
repost: o.repost renote: o.renote
} }
}).$mount(); }).$mount();
document.body.appendChild(vm.$el); document.body.appendChild(vm.$el);

View File

@ -28,7 +28,7 @@ import MkSelectDrive from './views/pages/selectdrive.vue';
import MkDrive from './views/pages/drive.vue'; import MkDrive from './views/pages/drive.vue';
import MkHomeCustomize from './views/pages/home-customize.vue'; import MkHomeCustomize from './views/pages/home-customize.vue';
import MkMessagingRoom from './views/pages/messaging-room.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 MkSearch from './views/pages/search.vue';
import MkOthello from './views/pages/othello.vue'; import MkOthello from './views/pages/othello.vue';
@ -57,7 +57,7 @@ init(async (launch) => {
{ path: '/othello', component: MkOthello }, { path: '/othello', component: MkOthello },
{ path: '/othello/:game', component: MkOthello }, { path: '/othello/:game', component: MkOthello },
{ path: '/@:user', component: MkUser }, { 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); setTimeout(n.close.bind(n), 5000);
}); });
connection.on('mention', post => { connection.on('mention', note => {
const _n = composeNotification('mention', post); const _n = composeNotification('mention', note);
const n = new Notification(_n.title, { const n = new Notification(_n.title, {
body: _n.body, body: _n.body,
icon: _n.icon icon: _n.icon
@ -123,8 +123,8 @@ function registerNotifications(stream: HomeStreamManager) {
setTimeout(n.close.bind(n), 6000); setTimeout(n.close.bind(n), 6000);
}); });
connection.on('reply', post => { connection.on('reply', note => {
const _n = composeNotification('reply', post); const _n = composeNotification('reply', note);
const n = new Notification(_n.title, { const n = new Notification(_n.title, {
body: _n.body, body: _n.body,
icon: _n.icon icon: _n.icon
@ -132,8 +132,8 @@ function registerNotifications(stream: HomeStreamManager) {
setTimeout(n.close.bind(n), 6000); setTimeout(n.close.bind(n), 6000);
}); });
connection.on('quote', post => { connection.on('quote', note => {
const _n = composeNotification('quote', post); const _n = composeNotification('quote', note);
const n = new Notification(_n.title, { const n = new Notification(_n.title, {
body: _n.body, body: _n.body,
icon: _n.icon icon: _n.icon

View File

@ -29,7 +29,7 @@ import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: ['data'], props: ['data'],
created() { 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)); const peak = Math.max.apply(null, this.data.map(d => d.total));
let x = 0; let x = 0;

View File

@ -1,8 +1,8 @@
<template> <template>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown"> <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 <polyline
:points="pointsPost" :points="pointsNote"
fill="none" fill="none"
stroke-width="1" stroke-width="1"
stroke="#41ddde"/> stroke="#41ddde"/>
@ -12,7 +12,7 @@
stroke-width="1" stroke-width="1"
stroke="#f7796c"/> stroke="#f7796c"/>
<polyline <polyline
:points="pointsRepost" :points="pointsRenote"
fill="none" fill="none"
stroke-width="1" stroke-width="1"
stroke="#a1de41"/> stroke="#a1de41"/>
@ -48,24 +48,24 @@ export default Vue.extend({
viewBoxY: 60, viewBoxY: 60,
zoom: 1, zoom: 1,
pos: 0, pos: 0,
pointsPost: null, pointsNote: null,
pointsReply: null, pointsReply: null,
pointsRepost: null, pointsRenote: null,
pointsTotal: null pointsTotal: null
}; };
}, },
created() { created() {
this.data.reverse(); 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(); this.render();
}, },
methods: { methods: {
render() { render() {
const peak = Math.max.apply(null, this.data.map(d => d.total)); const peak = Math.max.apply(null, this.data.map(d => d.total));
if (peak != 0) { 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.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(' '); this.pointsTotal = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
} }
}, },

View File

@ -4,23 +4,23 @@ import ui from './ui.vue';
import uiNotification from './ui-notification.vue'; import uiNotification from './ui-notification.vue';
import home from './home.vue'; import home from './home.vue';
import timeline from './timeline.vue'; import timeline from './timeline.vue';
import posts from './posts.vue'; import notes from './notes.vue';
import subPostContent from './sub-post-content.vue'; import subNoteContent from './sub-note-content.vue';
import window from './window.vue'; import window from './window.vue';
import postFormWindow from './post-form-window.vue'; import noteFormWindow from './post-form-window.vue';
import repostFormWindow from './repost-form-window.vue'; import renoteFormWindow from './renote-form-window.vue';
import analogClock from './analog-clock.vue'; import analogClock from './analog-clock.vue';
import ellipsisIcon from './ellipsis-icon.vue'; import ellipsisIcon from './ellipsis-icon.vue';
import mediaImage from './media-image.vue'; import mediaImage from './media-image.vue';
import mediaImageDialog from './media-image-dialog.vue'; import mediaImageDialog from './media-image-dialog.vue';
import mediaVideo from './media-video.vue'; import mediaVideo from './media-video.vue';
import notifications from './notifications.vue'; import notifications from './notifications.vue';
import postForm from './post-form.vue'; import noteForm from './post-form.vue';
import repostForm from './repost-form.vue'; import renoteForm from './renote-form.vue';
import followButton from './follow-button.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 drive from './drive.vue';
import postDetail from './post-detail.vue'; import noteDetail from './note-detail.vue';
import settings from './settings.vue'; import settings from './settings.vue';
import calendar from './calendar.vue'; import calendar from './calendar.vue';
import activity from './activity.vue'; import activity from './activity.vue';
@ -34,23 +34,23 @@ Vue.component('mk-ui', ui);
Vue.component('mk-ui-notification', uiNotification); Vue.component('mk-ui-notification', uiNotification);
Vue.component('mk-home', home); Vue.component('mk-home', home);
Vue.component('mk-timeline', timeline); Vue.component('mk-timeline', timeline);
Vue.component('mk-posts', posts); Vue.component('mk-notes', notes);
Vue.component('mk-sub-post-content', subPostContent); Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-window', window); Vue.component('mk-window', window);
Vue.component('mk-post-form-window', postFormWindow); Vue.component('mk-post-form-window', noteFormWindow);
Vue.component('mk-repost-form-window', repostFormWindow); Vue.component('mk-renote-form-window', renoteFormWindow);
Vue.component('mk-analog-clock', analogClock); Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-ellipsis-icon', ellipsisIcon); Vue.component('mk-ellipsis-icon', ellipsisIcon);
Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-image-dialog', mediaImageDialog); Vue.component('mk-media-image-dialog', mediaImageDialog);
Vue.component('mk-media-video', mediaVideo); Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-notifications', notifications); Vue.component('mk-notifications', notifications);
Vue.component('mk-post-form', postForm); Vue.component('mk-post-form', noteForm);
Vue.component('mk-repost-form', repostForm); Vue.component('mk-renote-form', renoteForm);
Vue.component('mk-follow-button', followButton); 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-drive', drive);
Vue.component('mk-post-detail', postDetail); Vue.component('mk-note-detail', noteDetail);
Vue.component('mk-settings', settings); Vue.component('mk-settings', settings);
Vue.component('mk-calendar', calendar); Vue.component('mk-calendar', calendar);
Vue.component('mk-activity', activity); Vue.component('mk-activity', activity);

View File

@ -7,12 +7,12 @@
<div class="fetching" v-if="fetching"> <div class="fetching" v-if="fetching">
<mk-ellipsis-icon/> <mk-ellipsis-icon/>
</div> </div>
<p class="empty" v-if="posts.length == 0 && !fetching"> <p class="empty" v-if="notes.length == 0 && !fetching">
%fa:R comments% %fa:R comments%
<span v-if="mode == 'all'">あなた宛ての投稿はありません</span> <span v-if="mode == 'all'">あなた宛ての投稿はありません</span>
<span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません</span> <span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません</span>
</p> </p>
<mk-posts :posts="posts" ref="timeline"/> <mk-notes :notes="notes" ref="timeline"/>
</div> </div>
</template> </template>
@ -24,7 +24,7 @@ export default Vue.extend({
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
mode: 'all', mode: 'all',
posts: [] notes: []
}; };
}, },
watch: { watch: {
@ -56,23 +56,23 @@ export default Vue.extend({
}, },
fetch(cb?) { fetch(cb?) {
this.fetching = true; this.fetching = true;
this.posts = []; this.notes = [];
(this as any).api('posts/mentions', { (this as any).api('notes/mentions', {
following: this.mode == 'following' following: this.mode == 'following'
}).then(posts => { }).then(notes => {
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
if (cb) cb(); if (cb) cb();
}); });
}, },
more() { 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.moreFetching = true;
(this as any).api('posts/mentions', { (this as any).api('notes/mentions', {
following: this.mode == 'following', following: this.mode == 'following',
untilId: this.posts[this.posts.length - 1].id untilId: this.notes[this.notes.length - 1].id
}).then(posts => { }).then(notes => {
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
this.moreFetching = false; this.moreFetching = false;
}); });
} }

View File

@ -1,24 +1,24 @@
<template> <template>
<div class="sub" :title="title"> <div class="sub" :title="title">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <header>
<div class="left"> <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> <span class="username">@{{ acct }}</span>
</div> </div>
<div class="right"> <div class="right">
<router-link class="time" :to="`/@${acct}/${post.id}`"> <router-link class="time" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</div> </div>
</header> </header>
<div class="body"> <div class="body">
<mk-post-html v-if="post.text" :text="post.text" :i="os.i" :class="$style.text"/> <mk-note-html v-if="note.text" :text="note.text" :i="os.i" :class="$style.text"/>
<div class="media" v-if="post.media > 0"> <div class="media" v-if="note.media > 0">
<mk-media-list :media-list="post.media"/> <mk-media-list :media-list="note.media"/>
</div> </div>
</div> </div>
</div> </div>
@ -32,16 +32,16 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
title(): string { title(): string {
return dateStringify(this.post.createdAt); return dateStringify(this.note.createdAt);
} }
} }
}); });

View File

@ -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>

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="mk-post-preview" :title="title"> <div class="mk-note-preview" :title="title">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <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> <span class="username">@{{ acct }}</span>
<router-link class="time" :to="`/@${acct}/${post.id}`"> <router-link class="time" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-sub-post-content class="text" :post="post"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
</div> </div>
</div> </div>
@ -25,23 +25,23 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
title(): string { title(): string {
return dateStringify(this.post.createdAt); return dateStringify(this.note.createdAt);
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-post-preview .mk-note-preview
font-size 0.9em font-size 0.9em
background #fff background #fff

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="sub" :title="title"> <div class="sub" :title="title">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <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> <span class="username">@{{ acct }}</span>
<router-link class="created-at" :to="`/@${acct}/${post.id}`"> <router-link class="created-at" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-sub-post-content class="text" :post="post"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
</div> </div>
</div> </div>
@ -25,16 +25,16 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name(): string { name(): string {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
title(): string { title(): string {
return dateStringify(this.post.createdAt); return dateStringify(this.note.createdAt);
} }
} }
}); });

View File

@ -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>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="mk-posts"> <div class="mk-notes">
<template v-for="(post, i) in _posts"> <template v-for="(note, i) in _notes">
<x-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
<p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> <p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<span>%fa:angle-up%{{ post._datetext }}</span> <span>%fa:angle-up%{{ note._datetext }}</span>
<span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
<footer> <footer>
@ -15,26 +15,26 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XPost from './posts.post.vue'; import XNote from './notes.note.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XPost XNote
}, },
props: { props: {
posts: { notes: {
type: Array, type: Array,
default: () => [] default: () => []
} }
}, },
computed: { computed: {
_posts(): any[] { _notes(): any[] {
return (this.posts as any).map(post => { return (this.notes as any).map(note => {
const date = new Date(post.createdAt).getDate(); const date = new Date(note.createdAt).getDate();
const month = new Date(post.createdAt).getMonth() + 1; const month = new Date(note.createdAt).getMonth() + 1;
post._date = date; note._date = date;
post._datetext = `${month}${date}`; note._datetext = `${month}${date}`;
return post; return note;
}); });
} }
}, },
@ -42,15 +42,15 @@ export default Vue.extend({
focus() { focus() {
(this.$el as any).children[0].focus(); (this.$el as any).children[0].focus();
}, },
onPostUpdated(i, post) { onNoteUpdated(i, note) {
Vue.set((this as any).posts, i, post); Vue.set((this as any).notes, i, note);
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-posts .mk-notes
> .date > .date
display block display block

View File

@ -13,33 +13,33 @@
<mk-reaction-icon :reaction="notification.reaction"/> <mk-reaction-icon :reaction="notification.reaction"/>
<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link>
</p> </p>
<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%
</router-link> </router-link>
</div> </div>
</template> </template>
<template v-if="notification.type == 'repost'"> <template v-if="notification.type == 'renote'">
<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">
<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link> </router-link>
<div class="text"> <div class="text">
<p>%fa:retweet% <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> </p>
<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% %fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%
</router-link> </router-link>
</div> </div>
</template> </template>
<template v-if="notification.type == 'quote'"> <template v-if="notification.type == 'quote'">
<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">
<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link> </router-link>
<div class="text"> <div class="text">
<p>%fa:quote-left% <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> </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> </div>
</template> </template>
<template v-if="notification.type == 'follow'"> <template v-if="notification.type == 'follow'">
@ -53,25 +53,25 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'reply'"> <template v-if="notification.type == 'reply'">
<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">
<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link> </router-link>
<div class="text"> <div class="text">
<p>%fa:reply% <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> </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> </div>
</template> </template>
<template v-if="notification.type == 'mention'"> <template v-if="notification.type == 'mention'">
<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> <router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">
<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link> </router-link>
<div class="text"> <div class="text">
<p>%fa:at% <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> </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> </div>
</template> </template>
<template v-if="notification.type == 'poll_vote'"> <template v-if="notification.type == 'poll_vote'">
@ -80,8 +80,8 @@
</router-link> </router-link>
<div class="text"> <div class="text">
<p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p> <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}`"> <router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%
</router-link> </router-link>
</div> </div>
</template> </template>
@ -103,7 +103,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getAcct from '../../../../../acct/render'; 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'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
@ -115,7 +115,7 @@ export default Vue.extend({
moreNotifications: false, moreNotifications: false,
connection: null, connection: null,
connectionId: null, connectionId: null,
getPostSummary getNoteSummary
}; };
}, },
computed: { computed: {
@ -241,10 +241,10 @@ export default Vue.extend({
i, .mk-reaction-icon i, .mk-reaction-icon
margin-right 4px margin-right 4px
.post-preview .note-preview
color rgba(0, 0, 0, 0.7) color rgba(0, 0, 0, 0.7)
.post-ref .note-ref
color rgba(0, 0, 0, 0.7) color rgba(0, 0, 0, 0.7)
[data-fa] [data-fa]
@ -254,7 +254,7 @@ export default Vue.extend({
display inline-block display inline-block
margin-right 3px margin-right 3px
&.repost, &.quote &.renote, &.quote
.text p i .text p i
color #77B255 color #77B255

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-post-detail" :title="title"> <div class="mk-note-detail" :title="title">
<button <button
class="read-more" class="read-more"
v-if="p.reply && p.reply.replyId && context == null" v-if="p.reply && p.reply.replyId && context == null"
@ -11,19 +11,19 @@
<template v-if="contextFetching">%fa:spinner .pulse%</template> <template v-if="contextFetching">%fa:spinner .pulse%</template>
</button> </button>
<div class="context"> <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>
<div class="reply-to" v-if="p.reply"> <div class="reply-to" v-if="p.reply">
<x-sub :post="p.reply"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="repost" v-if="isRepost"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId">
<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> </router-link>
%fa:retweet% %fa:retweet%
<router-link class="name" :href="`/@${acct}`">{{ getUserName(post.user) }}</router-link> <router-link class="name" :href="`/@${acct}`">{{ getUserName(note.user) }}</router-link>
がRepost がRenote
</p> </p>
</div> </div>
<article> <article>
@ -38,28 +38,28 @@
</router-link> </router-link>
</header> </header>
<div class="body"> <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"> <div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/> <mk-media-list :media-list="p.media"/>
</div> </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"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/>
<div class="tags" v-if="p.tags && p.tags.length > 0"> <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> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
</div> </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> <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="map" v-if="p.geo" ref="map"></div>
<div class="repost" v-if="p.repost"> <div class="renote" v-if="p.renote">
<mk-post-preview :post="p.repost"/> <mk-note-preview :note="p.renote"/>
</div> </div>
</div> </div>
<footer> <footer>
<mk-reactions-viewer :post="p"/> <mk-reactions-viewer :note="p"/>
<button @click="reply" title="返信"> <button @click="reply" title="返信">
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
</button> </button>
<button @click="repost" title="Repost"> <button @click="renote" title="Renote">
%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
</button> </button>
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="リアクション"> <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> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
@ -70,7 +70,7 @@
</footer> </footer>
</article> </article>
<div class="replies" v-if="!compact"> <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>
</div> </div>
</template> </template>
@ -83,10 +83,10 @@ import getUserName from '../../../../../renderers/get-user-name';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import MkPostFormWindow from './post-form-window.vue'; import MkPostFormWindow from './post-form-window.vue';
import MkRepostFormWindow from './repost-form-window.vue'; import MkRenoteFormWindow from './renote-form-window.vue';
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 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({ export default Vue.extend({
components: { components: {
@ -94,7 +94,7 @@ export default Vue.extend({
}, },
props: { props: {
post: { note: {
type: Object, type: Object,
required: true required: true
}, },
@ -112,14 +112,14 @@ export default Vue.extend({
}, },
computed: { computed: {
isRepost(): boolean { isRenote(): boolean {
return (this.post.repost && return (this.note.renote &&
this.post.text == null && this.note.text == null &&
this.post.mediaIds == null && this.note.mediaIds == null &&
this.post.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRepost ? this.post.repost : this.post; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
@ -132,10 +132,10 @@ export default Vue.extend({
return dateStringify(this.p.createdAt); return dateStringify(this.p.createdAt);
}, },
acct(): string { acct(): string {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name(): string { name(): string {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
pAcct(): string { pAcct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
@ -158,8 +158,8 @@ export default Vue.extend({
mounted() { mounted() {
// Get replies // Get replies
if (!this.compact) { if (!this.compact) {
(this as any).api('posts/replies', { (this as any).api('notes/replies', {
postId: this.p.id, noteId: this.p.id,
limit: 8 limit: 8
}).then(replies => { }).then(replies => {
this.replies = replies; this.replies = replies;
@ -190,8 +190,8 @@ export default Vue.extend({
this.contextFetching = true; this.contextFetching = true;
// Fetch context // Fetch context
(this as any).api('posts/context', { (this as any).api('notes/context', {
postId: this.p.replyId noteId: this.p.replyId
}).then(context => { }).then(context => {
this.contextFetching = false; this.contextFetching = false;
this.context = context.reverse(); this.context = context.reverse();
@ -202,21 +202,21 @@ export default Vue.extend({
reply: this.p reply: this.p
}); });
}, },
repost() { renote() {
(this as any).os.new(MkRepostFormWindow, { (this as any).os.new(MkRenoteFormWindow, {
post: this.p note: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
post: this.p note: this.p
}); });
}, },
menu() { menu() {
(this as any).os.new(MkPostMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
post: this.p note: this.p
}); });
} }
} }
@ -226,7 +226,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.mk-post-detail .mk-note-detail
margin 0 margin 0
padding 0 padding 0
overflow hidden overflow hidden
@ -263,7 +263,7 @@ export default Vue.extend({
> * > *
border-bottom 1px solid #eef0f2 border-bottom 1px solid #eef0f2
> .repost > .renote
color #9dbb00 color #9dbb00
background linear-gradient(to bottom, #edfde2 0%, #fff 100%) background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
@ -355,10 +355,10 @@ export default Vue.extend({
> .body > .body
padding 8px 0 padding 8px 0
> .repost > .renote
margin 8px 0 margin 8px 0
> .mk-post-preview > .mk-note-preview
padding 16px padding 16px
border dashed 1px #c0dac6 border dashed 1px #c0dac6
border-radius 8px border-radius 8px

View File

@ -2,13 +2,13 @@
<mk-window ref="window" is-modal @closed="$destroy"> <mk-window ref="window" is-modal @closed="$destroy">
<span slot="header"> <span slot="header">
<span :class="$style.icon" v-if="geo">%fa:map-marker-alt%</span> <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 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="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 :class="$style.count" v-if="uploadings.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
</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" <mk-post-form ref="form"
:reply="reply" :reply="reply"
@posted="onPosted" @posted="onPosted"
@ -70,7 +70,7 @@ export default Vue.extend({
&:after &:after
content ')' content ')'
.postPreview .notePreview
margin 16px 22px margin 16px 22px
</style> </style>

View File

@ -46,7 +46,7 @@ export default Vue.extend({
components: { components: {
XDraggable XDraggable
}, },
props: ['reply', 'repost'], props: ['reply', 'renote'],
data() { data() {
return { return {
posting: false, posting: false,
@ -61,28 +61,28 @@ export default Vue.extend({
}, },
computed: { computed: {
draftId(): string { draftId(): string {
return this.repost return this.renote
? 'repost:' + this.repost.id ? 'renote:' + this.renote.id
: this.reply : this.reply
? 'reply:' + this.reply.id ? 'reply:' + this.reply.id
: 'post'; : 'note';
}, },
placeholder(): string { placeholder(): string {
return this.repost return this.renote
? '%i18n:desktop.tags.mk-post-form.quote-placeholder%' ? '%i18n:desktop.tags.mk-post-form.quote-placeholder%'
: this.reply : this.reply
? '%i18n:desktop.tags.mk-post-form.reply-placeholder%' ? '%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 { submitText(): string {
return this.repost return this.renote
? '%i18n:desktop.tags.mk-post-form.repost%' ? '%i18n:desktop.tags.mk-post-form.renote%'
: this.reply : this.reply
? '%i18n:desktop.tags.mk-post-form.reply%' ? '%i18n:desktop.tags.mk-post-form.reply%'
: '%i18n:desktop.tags.mk-post-form.post%'; : '%i18n:desktop.tags.mk-post-form.note%';
}, },
canPost(): boolean { 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: { watch: {
@ -217,11 +217,11 @@ export default Vue.extend({
post() { post() {
this.posting = true; this.posting = true;
(this as any).api('posts/create', { (this as any).api('notes/create', {
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
replyId: this.reply ? this.reply.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, poll: this.poll ? (this.$refs.poll as any).get() : undefined,
geo: this.geo ? { geo: this.geo ? {
coordinates: [this.geo.longitude, this.geo.latitude], coordinates: [this.geo.longitude, this.geo.latitude],
@ -235,17 +235,17 @@ export default Vue.extend({
this.clear(); this.clear();
this.deleteDraft(); this.deleteDraft();
this.$emit('posted'); this.$emit('posted');
(this as any).apis.notify(this.repost (this as any).apis.notify(this.renote
? '%i18n:desktop.tags.mk-post-form.reposted%' ? '%i18n:desktop.tags.mk-post-form.reposted%'
: this.reply : this.reply
? '%i18n:desktop.tags.mk-post-form.replied%' ? '%i18n:desktop.tags.mk-post-form.replied%'
: '%i18n:desktop.tags.mk-post-form.posted%'); : '%i18n:desktop.tags.mk-post-form.posted%');
}).catch(err => { }).catch(err => {
(this as any).apis.notify(this.repost (this as any).apis.notify(this.renote
? '%i18n:desktop.tags.mk-post-form.repost-failed%' ? '%i18n:desktop.tags.mk-post-form.renote-failed%'
: this.reply : this.reply
? '%i18n:desktop.tags.mk-post-form.reply-failed%' ? '%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(() => { }).then(() => {
this.posting = false; this.posting = false;
}); });

View File

@ -1,19 +1,19 @@
<template> <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"> <div class="reply-to" v-if="p.reply">
<x-sub :post="p.reply"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="repost" v-if="isRepost"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> <router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId">
<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> </router-link>
%fa:retweet% %fa:retweet%
<span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</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="post.userId">{{ getUserName(post.user) }}</a> <a class="name" :href="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.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('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
</p> </p>
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="`/@${acct}`"> <router-link class="avatar-anchor" :to="`/@${acct}`">
@ -38,38 +38,38 @@
</p> </p>
<div class="text"> <div class="text">
<a class="reply" v-if="p.reply">%fa:reply%</a> <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"/> <mk-note-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.repost">RP:</a> <a class="rp" v-if="p.renote">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/> <mk-media-list :media-list="p.media"/>
</div> </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"> <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> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
</div> </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> <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="map" v-if="p.geo" ref="map"></div>
<div class="repost" v-if="p.repost"> <div class="renote" v-if="p.renote">
<mk-post-preview :post="p.repost"/> <mk-note-preview :note="p.renote"/>
</div> </div>
<mk-url-preview v-for="url in urls" :url="url" :key="url"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/>
</div> </div>
<footer> <footer>
<mk-reactions-viewer :post="p" ref="reactionsViewer"/> <mk-reactions-viewer :note="p" ref="reactionsViewer"/>
<button @click="reply" title="%i18n:desktop.tags.mk-timeline-post.reply%"> <button @click="reply" title="%i18n:desktop.tags.mk-timeline-note.reply%">
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
</button> </button>
<button @click="repost" title="%i18n:desktop.tags.mk-timeline-post.repost%"> <button @click="renote" title="%i18n:desktop.tags.mk-timeline-note.renote%">
%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
</button> </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> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
</button> </button>
<button @click="menu" ref="menuButton"> <button @click="menu" ref="menuButton">
%fa:ellipsis-h% %fa:ellipsis-h%
</button> </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-down%</template>
<template v-if="isDetailOpened">%fa:caret-up%</template> <template v-if="isDetailOpened">%fa:caret-up%</template>
</button> </button>
@ -77,7 +77,7 @@
</div> </div>
</article> </article>
<div class="detail" v-if="isDetailOpened"> <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>
</div> </div>
</template> </template>
@ -90,10 +90,10 @@ import getUserName from '../../../../../renderers/get-user-name';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import MkPostFormWindow from './post-form-window.vue'; import MkPostFormWindow from './post-form-window.vue';
import MkRepostFormWindow from './repost-form-window.vue'; import MkRenoteFormWindow from './renote-form-window.vue';
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 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) { function focus(el, fn) {
const target = fn(el); const target = fn(el);
@ -111,7 +111,7 @@ export default Vue.extend({
XSub XSub
}, },
props: ['post'], props: ['note'],
data() { data() {
return { return {
@ -128,14 +128,14 @@ export default Vue.extend({
name(): string { name(): string {
return getUserName(this.p.user); return getUserName(this.p.user);
}, },
isRepost(): boolean { isRenote(): boolean {
return (this.post.repost && return (this.note.renote &&
this.post.text == null && this.note.text == null &&
this.post.mediaIds == null && this.note.mediaIds == null &&
this.post.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRepost ? this.post.repost : this.post; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
@ -211,7 +211,7 @@ export default Vue.extend({
type: 'capture', type: 'capture',
id: this.p.id 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) { decapture(withHandler = false) {
@ -220,18 +220,18 @@ export default Vue.extend({
type: 'decapture', type: 'decapture',
id: this.p.id id: this.p.id
}); });
if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
} }
}, },
onStreamConnected() { onStreamConnected() {
this.capture(); this.capture();
}, },
onStreamPostUpdated(data) { onStreamNoteUpdated(data) {
const post = data.post; const note = data.note;
if (post.id == this.post.id) { if (note.id == this.note.id) {
this.$emit('update:post', post); this.$emit('update:note', note);
} else if (post.id == this.post.repostId) { } else if (note.id == this.note.renoteId) {
this.post.repost = post; this.note.renote = note;
} }
}, },
reply() { reply() {
@ -239,21 +239,21 @@ export default Vue.extend({
reply: this.p reply: this.p
}); });
}, },
repost() { renote() {
(this as any).os.new(MkRepostFormWindow, { (this as any).os.new(MkRenoteFormWindow, {
post: this.p note: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
post: this.p note: this.p
}); });
}, },
menu() { menu() {
(this as any).os.new(MkPostMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
post: this.p note: this.p
}); });
}, },
onKeydown(e) { onKeydown(e) {
@ -274,7 +274,7 @@ export default Vue.extend({
case e.which == 81: // [q] case e.which == 81: // [q]
case e.which == 69: // [e] case e.which == 69: // [e]
this.repost(); this.renote();
break; break;
case e.which == 70: // [f] case e.which == 70: // [f]
@ -299,7 +299,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.post .note
margin 0 margin 0
padding 0 padding 0
background #fff background #fff
@ -309,7 +309,7 @@ export default Vue.extend({
border-top-left-radius 6px border-top-left-radius 6px
border-top-right-radius 6px border-top-right-radius 6px
> .repost > .renote
border-top-left-radius 6px border-top-left-radius 6px
border-top-right-radius 6px border-top-right-radius 6px
@ -330,7 +330,7 @@ export default Vue.extend({
border 2px solid rgba($theme-color, 0.3) border 2px solid rgba($theme-color, 0.3)
border-radius 4px border-radius 4px
> .repost > .renote
color #9dbb00 color #9dbb00
background linear-gradient(to bottom, #edfde2 0%, #fff 100%) background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
@ -369,7 +369,7 @@ export default Vue.extend({
padding 0 16px padding 0 16px
background rgba(0, 0, 0, 0.0125) background rgba(0, 0, 0, 0.0125)
> .mk-post-preview > .mk-note-preview
background transparent background transparent
> article > article
@ -529,10 +529,10 @@ export default Vue.extend({
> .mk-poll > .mk-poll
font-size 80% font-size 80%
> .repost > .renote
margin 8px 0 margin 8px 0
> .mk-post-preview > .mk-note-preview
padding 16px padding 16px
border dashed 1px #c0dac6 border dashed 1px #c0dac6
border-radius 8px border-radius 8px

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +1,7 @@
<template> <template>
<mk-window ref="window" is-modal @closed="$destroy"> <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> <span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-renote-form-window.title%</span>
<mk-repost-form ref="form" :post="post" @posted="onPosted" @canceled="onCanceled"/> <mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/>
</mk-window> </mk-window>
</template> </template>
@ -9,7 +9,7 @@
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
mounted() { mounted() {
document.addEventListener('keydown', this.onDocumentKeydown); document.addEventListener('keydown', this.onDocumentKeydown);
}, },

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="mk-repost-form"> <div class="mk-renote-form">
<mk-post-preview :post="post"/> <mk-note-preview :note="note"/>
<template v-if="!quote"> <template v-if="!quote">
<footer> <footer>
<a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-repost-form.quote%</a> <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-repost-form.cancel%</button> <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-repost-form.reposting%' : '%i18n:desktop.tags.mk-repost-form.repost%' }}</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> </footer>
</template> </template>
<template v-if="quote"> <template v-if="quote">
<mk-post-form ref="form" :repost="post" @posted="onChildFormPosted"/> <mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/>
</template> </template>
</div> </div>
</template> </template>
@ -18,7 +18,7 @@
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
data() { data() {
return { return {
wait: false, wait: false,
@ -28,13 +28,13 @@ export default Vue.extend({
methods: { methods: {
ok() { ok() {
this.wait = true; this.wait = true;
(this as any).api('posts/create', { (this as any).api('notes/create', {
repostId: this.post.id renoteId: this.note.id
}).then(data => { }).then(data => {
this.$emit('posted'); 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 => { }).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(() => { }).then(() => {
this.wait = false; this.wait = false;
}); });
@ -59,9 +59,9 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.mk-repost-form .mk-renote-form
> .mk-post-preview > .mk-note-preview
margin 16px 22px margin 16px 22px
> footer > footer

View File

@ -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>

View File

@ -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>

View File

@ -4,15 +4,15 @@
<div class="fetching" v-if="fetching"> <div class="fetching" v-if="fetching">
<mk-ellipsis-icon/> <mk-ellipsis-icon/>
</div> </div>
<p class="empty" v-if="posts.length == 0 && !fetching"> <p class="empty" v-if="notes.length == 0 && !fetching">
%fa:R comments%自分の投稿や自分がフォローしているユーザーの投稿が表示されます %fa:R comments%自分の投稿や自分がフォローしているユーザーの投稿が表示されます
</p> </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' }"> <button slot="footer" @click="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">もっと見る</template> <template v-if="!moreFetching">もっと見る</template>
<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template>
</button> </button>
</mk-posts> </mk-notes>
</div> </div>
</template> </template>
@ -26,7 +26,7 @@ export default Vue.extend({
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
existMore: false, existMore: false,
posts: [], notes: [],
connection: null, connection: null,
connectionId: null, connectionId: null,
date: null date: null
@ -41,7 +41,7 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); 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('follow', this.onChangeFollowing);
this.connection.on('unfollow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing);
@ -51,7 +51,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('post', this.onPost); this.connection.off('note', this.onNote);
this.connection.off('follow', this.onChangeFollowing); this.connection.off('follow', this.onChangeFollowing);
this.connection.off('unfollow', this.onChangeFollowing); this.connection.off('unfollow', this.onChangeFollowing);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
@ -63,45 +63,45 @@ export default Vue.extend({
fetch(cb?) { fetch(cb?) {
this.fetching = true; this.fetching = true;
(this as any).api('posts/timeline', { (this as any).api('notes/timeline', {
limit: 11, limit: 11,
untilDate: this.date ? this.date.getTime() : undefined untilDate: this.date ? this.date.getTime() : undefined
}).then(posts => { }).then(notes => {
if (posts.length == 11) { if (notes.length == 11) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} }
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
this.$emit('loaded'); this.$emit('loaded');
if (cb) cb(); if (cb) cb();
}); });
}, },
more() { 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.moreFetching = true;
(this as any).api('posts/timeline', { (this as any).api('notes/timeline', {
limit: 11, limit: 11,
untilId: this.posts[this.posts.length - 1].id untilId: this.notes[this.notes.length - 1].id
}).then(posts => { }).then(notes => {
if (posts.length == 11) { if (notes.length == 11) {
posts.pop(); notes.pop();
} else { } else {
this.existMore = false; this.existMore = false;
} }
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
this.moreFetching = false; this.moreFetching = false;
}); });
}, },
onPost(post) { onNote(note) {
// //
if ((this as any).os.isEnableSounds) { 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.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1;
sound.play(); sound.play();
} }
this.posts.unshift(post); this.notes.unshift(note);
}, },
onChangeFollowing() { onChangeFollowing() {
this.fetch(); this.fetch();

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="post"> <div class="note">
<button @click="post" title="%i18n:desktop.tags.mk-ui-header-post-button.post%">%fa:pencil-alt%</button> <button @click="post" title="%i18n:desktop.tags.mk-ui-header-note-button.note%">%fa:pencil-alt%</button>
</div> </div>
</template> </template>
@ -19,7 +19,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.post .note
display inline-block display inline-block
padding 8px padding 8px
height 100% height 100%

View File

@ -12,7 +12,7 @@
<div class="description">{{ u.description }}</div> <div class="description">{{ u.description }}</div>
<div class="status"> <div class="status">
<div> <div>
<p>投稿</p><a>{{ u.postsCount }}</a> <p>投稿</p><a>{{ u.notesCount }}</a>
</div> </div>
<div> <div>
<p>フォロー</p><a>{{ u.followingCount }}</a> <p>フォロー</p><a>{{ u.followingCount }}</a>

View File

@ -7,7 +7,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import getPostSummary from '../../../../../renderers/get-post-summary'; import getNoteSummary from '../../../../../renderers/get-note-summary';
export default Vue.extend({ export default Vue.extend({
props: { props: {
@ -29,13 +29,13 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); 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); document.addEventListener('visibilitychange', this.onVisibilitychange, false);
Progress.start(); Progress.start();
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('post', this.onStreamPost); this.connection.off('note', this.onStreamNote);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
document.removeEventListener('visibilitychange', this.onVisibilitychange); document.removeEventListener('visibilitychange', this.onVisibilitychange);
}, },
@ -44,10 +44,10 @@ export default Vue.extend({
Progress.done(); Progress.done();
}, },
onStreamPost(post) { onStreamNote(note) {
if (document.hidden && post.userId != (this as any).os.i.id) { if (document.hidden && note.userId != (this as any).os.i.id) {
this.unreadCount++; this.unreadCount++;
document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
} }
}, },

View File

@ -1,9 +1,9 @@
<template> <template>
<mk-ui> <mk-ui>
<main v-if="!fetching"> <main v-if="!fetching">
<a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:desktop.tags.mk-post-page.next%</a> <a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:desktop.tags.mk-note-page.next%</a>
<mk-post-detail :post="post"/> <mk-note-detail :note="note"/>
<a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:desktop.tags.mk-post-page.prev%</a> <a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:desktop.tags.mk-note-page.prev%</a>
</main> </main>
</mk-ui> </mk-ui>
</template> </template>
@ -16,7 +16,7 @@ export default Vue.extend({
data() { data() {
return { return {
fetching: true, fetching: true,
post: null note: null
}; };
}, },
watch: { watch: {
@ -30,10 +30,10 @@ export default Vue.extend({
Progress.start(); Progress.start();
this.fetching = true; this.fetching = true;
(this as any).api('posts/show', { (this as any).api('notes/show', {
postId: this.$route.params.post noteId: this.$route.params.note
}).then(post => { }).then(note => {
this.post = post; this.note = note;
this.fetching = false; this.fetching = false;
Progress.done(); Progress.done();
@ -60,7 +60,7 @@ main
> [data-fa] > [data-fa]
margin-right 4px margin-right 4px
> .mk-post-detail > .mk-note-detail
margin 0 auto margin 0 auto
width 640px width 640px

View File

@ -7,12 +7,12 @@
<mk-ellipsis-icon/> <mk-ellipsis-icon/>
</div> </div>
<p :class="$style.empty" v-if="!fetching && empty">%fa:search%{{ q }}に関する投稿は見つかりませんでした</p> <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"> <div slot="footer">
<template v-if="!moreFetching">%fa:search%</template> <template v-if="!moreFetching">%fa:search%</template>
<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template>
</div> </div>
</mk-posts> </mk-notes>
</mk-ui> </mk-ui>
</template> </template>
@ -30,7 +30,7 @@ export default Vue.extend({
moreFetching: false, moreFetching: false,
existMore: false, existMore: false,
offset: 0, offset: 0,
posts: [] notes: []
}; };
}, },
watch: { watch: {
@ -38,7 +38,7 @@ export default Vue.extend({
}, },
computed: { computed: {
empty(): boolean { empty(): boolean {
return this.posts.length == 0; return this.notes.length == 0;
}, },
q(): string { q(): string {
return this.$route.query.q; return this.$route.query.q;
@ -66,33 +66,33 @@ export default Vue.extend({
this.fetching = true; this.fetching = true;
Progress.start(); Progress.start();
(this as any).api('posts/search', Object.assign({ (this as any).api('notes/search', Object.assign({
limit: limit + 1, limit: limit + 1,
offset: this.offset offset: this.offset
}, parse(this.q))).then(posts => { }, parse(this.q))).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} }
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
Progress.done(); Progress.done();
}); });
}, },
more() { 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.offset += limit;
this.moreFetching = true; this.moreFetching = true;
return (this as any).api('posts/search', Object.assign({ return (this as any).api('notes/search', Object.assign({
limit: limit + 1, limit: limit + 1,
offset: this.offset offset: this.offset
}, parse(this.q))).then(posts => { }, parse(this.q))).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
} else { } else {
this.existMore = false; this.existMore = false;
} }
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
this.moreFetching = false; this.moreFetching = false;
}); });
}, },
@ -111,7 +111,7 @@ export default Vue.extend({
margin 0 auto margin 0 auto
color #555 color #555
.posts .notes
max-width 600px max-width 600px
margin 0 auto margin 0 auto
border solid 1px rgba(0, 0, 0, 0.075) border solid 1px rgba(0, 0, 0, 0.075)

View File

@ -9,7 +9,7 @@
</div> </div>
</div> </div>
<main> <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"/> <x-timeline class="timeline" ref="tl" :user="user"/>
</main> </main>
<div> <div>

View File

@ -22,13 +22,13 @@ export default Vue.extend({
}; };
}, },
mounted() { mounted() {
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
withMedia: true, withMedia: true,
limit: 9 limit: 9
}).then(posts => { }).then(notes => {
posts.forEach(post => { notes.forEach(note => {
post.media.forEach(media => { note.media.forEach(media => {
if (this.images.length < 9) this.images.push(media); if (this.images.length < 9) this.images.push(media);
}); });
}); });

View File

@ -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> <p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screenName}`" target="_blank">@{{ user.account.twitter.screenName }}</a></p>
</div> </div>
<div class="status"> <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="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> <p class="followers">%fa:angle-right%<a @click="showFollowers">{{ user.followersCount }}</a>人の<b>フォロワー</b></p>
</div> </div>

View File

@ -8,12 +8,12 @@
<mk-ellipsis-icon/> <mk-ellipsis-icon/>
</div> </div>
<p class="empty" v-if="empty">%fa:R comments%このユーザーはまだ何も投稿していないようです</p> <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"> <div slot="footer">
<template v-if="!moreFetching">%fa:moon%</template> <template v-if="!moreFetching">%fa:moon%</template>
<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> <template v-if="moreFetching">%fa:spinner .pulse .fw%</template>
</div> </div>
</mk-posts> </mk-notes>
</div> </div>
</template> </template>
@ -27,7 +27,7 @@ export default Vue.extend({
moreFetching: false, moreFetching: false,
mode: 'default', mode: 'default',
unreadCount: 0, unreadCount: 0,
posts: [], notes: [],
date: null date: null
}; };
}, },
@ -38,7 +38,7 @@ export default Vue.extend({
}, },
computed: { computed: {
empty(): boolean { empty(): boolean {
return this.posts.length == 0; return this.notes.length == 0;
} }
}, },
mounted() { mounted() {
@ -60,26 +60,26 @@ export default Vue.extend({
} }
}, },
fetch(cb?) { fetch(cb?) {
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : undefined,
with_replies: this.mode == 'with-replies' with_replies: this.mode == 'with-replies'
}).then(posts => { }).then(notes => {
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
if (cb) cb(); if (cb) cb();
}); });
}, },
more() { 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.moreFetching = true;
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
with_replies: this.mode == 'with-replies', with_replies: this.mode == 'with-replies',
untilId: this.posts[this.posts.length - 1].id untilId: this.notes[this.notes.length - 1].id
}).then(posts => { }).then(notes => {
this.moreFetching = false; this.moreFetching = false;
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
}); });
}, },
onScroll() { onScroll() {

View File

@ -24,11 +24,11 @@ export default Vue.extend({
if (/^>>([0-9]+) /.test(this.text)) { if (/^>>([0-9]+) /.test(this.text)) {
const index = this.text.match(/^>>([0-9]+) /)[1]; 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.text = this.text.replace(/^>>([0-9]+) /, '');
} }
(this as any).api('posts/create', { (this as any).api('notes/create', {
text: this.text, text: this.text,
replyId: reply ? reply.id : undefined, replyId: reply ? reply.id : undefined,
channelId: (this.$parent as any).channel.id channelId: (this.$parent as any).channel.id

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="post"> <div class="note">
<header> <header>
<a class="index" @click="reply">{{ post.index }}:</a> <a class="index" @click="reply">{{ note.index }}:</a>
<router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ name }}</b></router-link> <router-link class="name" :to="`/@${acct}`" v-user-preview="note.user.id"><b>{{ name }}</b></router-link>
<span>ID:<i>{{ acct }}</i></span> <span>ID:<i>{{ acct }}</i></span>
</header> </header>
<div> <div>
<a v-if="post.reply">&gt;&gt;{{ post.reply.index }}</a> <a v-if="note.reply">&gt;&gt;{{ note.reply.index }}</a>
{{ post.text }} {{ note.text }}
<div class="media" v-if="post.media"> <div class="media" v-if="note.media">
<a v-for="file in post.media" :href="file.url" target="_blank"> <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"/> <img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
</a> </a>
</div> </div>
@ -23,25 +23,25 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
} }
}, },
methods: { methods: {
reply() { reply() {
this.$emit('reply', this.post); this.$emit('reply', this.note);
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.post .note
margin 0 margin 0
padding 0 padding 0
color #444 color #444

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="channel"> <div class="channel">
<p v-if="fetching">読み込み中<mk-ellipsis/></p> <p v-if="fetching">読み込み中<mk-ellipsis/></p>
<div v-if="!fetching" ref="posts" class="posts"> <div v-if="!fetching" ref="notes" class="notes">
<p v-if="posts.length == 0">まだ投稿がありません</p> <p v-if="notes.length == 0">まだ投稿がありません</p>
<x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/> <x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.id" @reply="reply"/>
</div> </div>
<x-form class="form" ref="form"/> <x-form class="form" ref="form"/>
</div> </div>
@ -13,18 +13,18 @@
import Vue from 'vue'; import Vue from 'vue';
import ChannelStream from '../../../common/scripts/streaming/channel'; import ChannelStream from '../../../common/scripts/streaming/channel';
import XForm from './channel.channel.form.vue'; 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({ export default Vue.extend({
components: { components: {
XForm, XForm,
XPost XNote
}, },
props: ['channel'], props: ['channel'],
data() { data() {
return { return {
fetching: true, fetching: true,
posts: [], notes: [],
connection: null connection: null
}; };
}, },
@ -43,10 +43,10 @@ export default Vue.extend({
zap() { zap() {
this.fetching = true; this.fetching = true;
(this as any).api('channels/posts', { (this as any).api('channels/notes', {
channelId: this.channel.id channelId: this.channel.id
}).then(posts => { }).then(notes => {
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
this.$nextTick(() => { this.$nextTick(() => {
@ -55,24 +55,24 @@ export default Vue.extend({
this.disconnect(); this.disconnect();
this.connection = new ChannelStream((this as any).os, this.channel.id); this.connection = new ChannelStream((this as any).os, this.channel.id);
this.connection.on('post', this.onPost); this.connection.on('note', this.onNote);
}); });
}, },
disconnect() { disconnect() {
if (this.connection) { if (this.connection) {
this.connection.off('post', this.onPost); this.connection.off('note', this.onNote);
this.connection.close(); this.connection.close();
} }
}, },
onPost(post) { onNote(note) {
this.posts.unshift(post); this.notes.unshift(note);
this.scrollToBottom(); this.scrollToBottom();
}, },
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) { reply(note) {
(this.$refs.form as any).text = `>>${ post.index } `; (this.$refs.form as any).text = `>>${ note.index } `;
} }
} }
}); });
@ -87,12 +87,12 @@ export default Vue.extend({
text-align center text-align center
color #aaa color #aaa
> .posts > .notes
height calc(100% - 38px) height calc(100% - 38px)
overflow auto overflow auto
font-size 0.9em font-size 0.9em
> .post > .note
border-bottom solid 1px #eee border-bottom solid 1px #eee
&:last-child &:last-child

View File

@ -7,7 +7,7 @@
<div class="poll" v-if="!fetching && poll != null"> <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 }`">{{ poll.text }}</router-link></p>
<p v-if="!poll.text"><router-link to="`/@${ acct }/${ poll.id }`">%fa:link%</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> </div>
<p class="empty" v-if="!fetching && poll == null">%i18n:desktop.tags.mk-recommended-polls-home-widget.nothing%</p> <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> <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.fetching = true;
this.poll = null; this.poll = null;
(this as any).api('posts/polls/recommendation', { (this as any).api('notes/polls/recommendation', {
limit: 1, limit: 1,
offset: this.offset offset: this.offset
}).then(posts => { }).then(notes => {
const poll = posts ? posts[0] : null; const poll = notes ? notes[0] : null;
if (poll == null) { if (poll == null) {
this.offset = 0; this.offset = 0;
} else { } else {

View File

@ -4,7 +4,7 @@
<p class="title">%fa:pencil-alt%%i18n:desktop.tags.mk-post-form-home-widget.title%</p> <p class="title">%fa:pencil-alt%%i18n:desktop.tags.mk-post-form-home-widget.title%</p>
</template> </template>
<textarea :disabled="posting" v-model="text" @keydown="onKeydown" placeholder="%i18n:desktop.tags.mk-post-form-home-widget.placeholder%"></textarea> <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> </div>
</template> </template>
@ -36,7 +36,7 @@ export default define({
post() { post() {
this.posting = true; this.posting = true;
(this as any).api('posts/create', { (this as any).api('notes/create', {
text: this.text text: this.text
}).then(data => { }).then(data => {
this.clear(); this.clear();

View File

@ -5,8 +5,8 @@
<button @click="fetch" title="%i18n:desktop.tags.mk-trends-home-widget.refresh%">%fa:sync%</button> <button @click="fetch" title="%i18n:desktop.tags.mk-trends-home-widget.refresh%">%fa:sync%</button>
</template> </template>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div class="post" v-else-if="post != null"> <div class="note" v-else-if="note != null">
<p class="text"><router-link :to="`/@${ acct }/${ post.id }`">{{ post.text }}</router-link></p> <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> <p class="author"><router-link :to="`/@${ acct }`">@{{ acct }}</router-link></p>
</div> </div>
<p class="empty" v-else>%i18n:desktop.tags.mk-trends-home-widget.nothing%</p> <p class="empty" v-else>%i18n:desktop.tags.mk-trends-home-widget.nothing%</p>
@ -25,12 +25,12 @@ export default define({
}).extend({ }).extend({
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
}, },
data() { data() {
return { return {
post: null, note: null,
fetching: true, fetching: true,
offset: 0 offset: 0
}; };
@ -44,23 +44,23 @@ export default define({
}, },
fetch() { fetch() {
this.fetching = true; this.fetching = true;
this.post = null; this.note = null;
(this as any).api('posts/trend', { (this as any).api('notes/trend', {
limit: 1, limit: 1,
offset: this.offset, offset: this.offset,
repost: false, renote: false,
reply: false, reply: false,
media: false, media: false,
poll: false poll: false
}).then(posts => { }).then(notes => {
const post = posts ? posts[0] : null; const note = notes ? notes[0] : null;
if (post == null) { if (note == null) {
this.offset = 0; this.offset = 0;
} else { } else {
this.offset++; this.offset++;
} }
this.post = post; this.note = note;
this.fetching = false; this.fetching = false;
}); });
} }
@ -103,7 +103,7 @@ export default define({
&:active &:active
color #999 color #999
> .post > .note
padding 16px padding 16px
font-size 12px font-size 12px
font-style oblique font-style oblique

View File

@ -27,7 +27,7 @@
<b-form-checkbox-group v-model="permission" stacked> <b-form-checkbox-group v-model="permission" stacked>
<b-form-checkbox value="account-read">アカウントの情報を見る</b-form-checkbox> <b-form-checkbox value="account-read">アカウントの情報を見る</b-form-checkbox>
<b-form-checkbox value="account-write">アカウントの情報を操作する</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="reaction-write">リアクションしたりリアクションをキャンセルする</b-form-checkbox>
<b-form-checkbox value="following-write">フォローしたりフォロー解除する</b-form-checkbox> <b-form-checkbox value="following-write">フォローしたりフォロー解除する</b-form-checkbox>
<b-form-checkbox value="drive-read">ドライブを見る</b-form-checkbox> <b-form-checkbox value="drive-read">ドライブを見る</b-form-checkbox>

View File

@ -1,24 +1,24 @@
import PostForm from '../views/components/post-form.vue'; import PostForm from '../views/components/post-form.vue';
//import RepostForm from '../views/components/repost-form.vue'; //import RenoteForm from '../views/components/renote-form.vue';
import getPostSummary from '../../../../renderers/get-post-summary'; import getNoteSummary from '../../../../renderers/get-note-summary';
export default (os) => (opts) => { export default (os) => (opts) => {
const o = opts || {}; const o = opts || {};
if (o.repost) { if (o.renote) {
/*const vm = new RepostForm({ /*const vm = new RenoteForm({
propsData: { propsData: {
repost: o.repost renote: o.renote
} }
}).$mount(); }).$mount();
vm.$once('cancel', recover); vm.$once('cancel', recover);
vm.$once('post', recover); vm.$once('note', recover);
document.body.appendChild(vm.$el);*/ 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; if (text == null) return;
os.api('posts/create', { os.api('notes/create', {
repostId: o.repost.id, renoteId: o.renote.id,
text: text == '' ? undefined : text text: text == '' ? undefined : text
}); });
} else { } else {
@ -36,7 +36,7 @@ export default (os) => (opts) => {
} }
}).$mount(); }).$mount();
vm.$once('cancel', recover); vm.$once('cancel', recover);
vm.$once('post', recover); vm.$once('note', recover);
document.body.appendChild(vm.$el); document.body.appendChild(vm.$el);
(vm as any).focus(); (vm as any).focus();
} }

View File

@ -25,7 +25,7 @@ import MkDrive from './views/pages/drive.vue';
import MkNotifications from './views/pages/notifications.vue'; import MkNotifications from './views/pages/notifications.vue';
import MkMessaging from './views/pages/messaging.vue'; import MkMessaging from './views/pages/messaging.vue';
import MkMessagingRoom from './views/pages/messaging-room.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 MkSearch from './views/pages/search.vue';
import MkFollowers from './views/pages/followers.vue'; import MkFollowers from './views/pages/followers.vue';
import MkFollowing from './views/pages/following.vue'; import MkFollowing from './views/pages/following.vue';
@ -68,7 +68,7 @@ init((launch) => {
{ path: '/@:user', component: MkUser }, { path: '/@:user', component: MkUser },
{ path: '/@:user/followers', component: MkFollowers }, { path: '/@:user/followers', component: MkFollowers },
{ path: '/@:user/following', component: MkFollowing }, { path: '/@:user/following', component: MkFollowing },
{ path: '/@:user/:post', component: MkPost } { path: '/@:user/:note', component: MkNote }
] ]
}); });

View File

@ -2,14 +2,14 @@
<div class="mk-activity"> <div class="mk-activity">
<svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> <svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none">
<g v-for="(d, i) in data"> <g v-for="(d, i) in data">
<rect width="0.8" :height="d.postsH" <rect width="0.8" :height="d.notesH"
:x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH" :x="i + 0.1" :y="1 - d.notesH - d.repliesH - d.renotesH"
fill="#41ddde"/> fill="#41ddde"/>
<rect width="0.8" :height="d.repliesH" <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"/> fill="#f7796c"/>
<rect width="0.8" :height="d.repostsH" <rect width="0.8" :height="d.renotesH"
:x="i + 0.1" :y="1 - d.repostsH" :x="i + 0.1" :y="1 - d.renotesH"
fill="#a1de41"/> fill="#a1de41"/>
</g> </g>
</svg> </svg>
@ -32,12 +32,12 @@ export default Vue.extend({
userId: this.user.id, userId: this.user.id,
limit: 30 limit: 30
}).then(data => { }).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)); this.peak = Math.max.apply(null, data.map(d => d.total));
data.forEach(d => { data.forEach(d => {
d.postsH = d.posts / this.peak; d.notesH = d.notes / this.peak;
d.repliesH = d.replies / this.peak; d.repliesH = d.replies / this.peak;
d.repostsH = d.reposts / this.peak; d.renotesH = d.renotes / this.peak;
}); });
data.reverse(); data.reverse();
this.data = data; this.data = data;

View File

@ -2,16 +2,16 @@ import Vue from 'vue';
import ui from './ui.vue'; import ui from './ui.vue';
import timeline from './timeline.vue'; import timeline from './timeline.vue';
import post from './post.vue'; import note from './note.vue';
import posts from './posts.vue'; import notes from './notes.vue';
import mediaImage from './media-image.vue'; import mediaImage from './media-image.vue';
import mediaVideo from './media-video.vue'; import mediaVideo from './media-video.vue';
import drive from './drive.vue'; import drive from './drive.vue';
import postPreview from './post-preview.vue'; import notePreview from './note-preview.vue';
import subPostContent from './sub-post-content.vue'; import subNoteContent from './sub-note-content.vue';
import postCard from './post-card.vue'; import noteCard from './note-card.vue';
import userCard from './user-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 followButton from './follow-button.vue';
import friendsMaker from './friends-maker.vue'; import friendsMaker from './friends-maker.vue';
import notification from './notification.vue'; import notification from './notification.vue';
@ -25,16 +25,16 @@ import widgetContainer from './widget-container.vue';
Vue.component('mk-ui', ui); Vue.component('mk-ui', ui);
Vue.component('mk-timeline', timeline); Vue.component('mk-timeline', timeline);
Vue.component('mk-post', post); Vue.component('mk-note', note);
Vue.component('mk-posts', posts); Vue.component('mk-notes', notes);
Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-video', mediaVideo); Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-drive', drive); Vue.component('mk-drive', drive);
Vue.component('mk-post-preview', postPreview); Vue.component('mk-note-preview', notePreview);
Vue.component('mk-sub-post-content', subPostContent); Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-post-card', postCard); Vue.component('mk-note-card', noteCard);
Vue.component('mk-user-card', userCard); 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-follow-button', followButton);
Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-friends-maker', friendsMaker);
Vue.component('mk-notification', notification); Vue.component('mk-notification', notification);

View File

@ -1,41 +1,41 @@
<template> <template>
<div class="mk-post-card"> <div class="mk-note-card">
<a :href="`/@${acct}/${post.id}`"> <a :href="`/@${acct}/${note.id}`">
<header> <header>
<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3> <img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3>
</header> </header>
<div> <div>
{{ text }} {{ text }}
</div> </div>
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</a> </a>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import summary from '../../../../../renderers/get-post-summary'; import summary from '../../../../../renderers/get-note-summary';
import getAcct from '../../../../../acct/render'; import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
text(): string { text(): string {
return summary(this.post); return summary(this.note);
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-post-card .mk-note-card
display inline-block display inline-block
width 150px width 150px
//height 120px //height 120px

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="root sub"> <div class="root sub">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <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> <span class="username">@{{ acct }}</span>
<router-link class="time" :to="`/@${acct}/${post.id}`"> <router-link class="time" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-sub-post-content class="text" :post="post"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
</div> </div>
</div> </div>
@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
} }
} }
}); });

View File

@ -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>

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="mk-post-preview"> <div class="mk-note-preview">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
<span class="username">@{{ acct }}</span> <span class="username">@{{ acct }}</span>
<router-link class="time" :to="`/@${acct}/${post.id}`"> <router-link class="time" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-sub-post-content class="text" :post="post"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
</div> </div>
</div> </div>
@ -24,20 +24,20 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-post-preview .mk-note-preview
margin 0 margin 0
padding 0 padding 0
font-size 0.9em font-size 0.9em

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="sub"> <div class="sub">
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="main"> <div class="main">
<header> <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> <span class="username">@{{ acct }}</span>
<router-link class="created-at" :to="`/@${acct}/${post.id}`"> <router-link class="created-at" :to="`/@${acct}/${note.id}`">
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
</header> </header>
<div class="body"> <div class="body">
<mk-sub-post-content class="text" :post="post"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
</div> </div>
</div> </div>
@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
props: ['post'], props: ['note'],
computed: { computed: {
acct() { acct() {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name() { name() {
return getUserName(this.post.user); return getUserName(this.note.user);
} }
} }
}); });

View File

@ -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>

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="mk-posts"> <div class="mk-notes">
<slot name="head"></slot> <slot name="head"></slot>
<slot></slot> <slot></slot>
<template v-for="(post, i) in _posts"> <template v-for="(note, i) in _notes">
<mk-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
<p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> <p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<span>%fa:angle-up%{{ post._datetext }}</span> <span>%fa:angle-up%{{ note._datetext }}</span>
<span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
<footer> <footer>
@ -20,25 +20,25 @@ import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: { props: {
posts: { notes: {
type: Array, type: Array,
default: () => [] default: () => []
} }
}, },
computed: { computed: {
_posts(): any[] { _notes(): any[] {
return (this.posts as any).map(post => { return (this.notes as any).map(note => {
const date = new Date(post.createdAt).getDate(); const date = new Date(note.createdAt).getDate();
const month = new Date(post.createdAt).getMonth() + 1; const month = new Date(note.createdAt).getMonth() + 1;
post._date = date; note._date = date;
post._datetext = `${month}${date}`; note._datetext = `${month}${date}`;
return post; return note;
}); });
} }
}, },
methods: { methods: {
onPostUpdated(i, post) { onNoteUpdated(i, note) {
Vue.set((this as any).posts, i, post); Vue.set((this as any).notes, i, note);
} }
} }
}); });
@ -47,7 +47,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.mk-posts .mk-notes
background #fff background #fff
border-radius 8px border-radius 8px
box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)

View File

@ -4,23 +4,23 @@
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<div class="text"> <div class="text">
<p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p> <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> </div>
</template> </template>
<template v-if="notification.type == 'repost'"> <template v-if="notification.type == 'renote'">
<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"> <div class="text">
<p>%fa:retweet%{{ posterName }}</p> <p>%fa:retweet%{{ noteerName }}</p>
<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p> <p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p>
</div> </div>
</template> </template>
<template v-if="notification.type == 'quote'"> <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"> <div class="text">
<p>%fa:quote-left%{{ posterName }}</p> <p>%fa:quote-left%{{ noteerName }}</p>
<p class="post-preview">{{ getPostSummary(notification.post) }}</p> <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
</div> </div>
</template> </template>
@ -32,18 +32,18 @@
</template> </template>
<template v-if="notification.type == 'reply'"> <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"> <div class="text">
<p>%fa:reply%{{ posterName }}</p> <p>%fa:reply%{{ noteerName }}</p>
<p class="post-preview">{{ getPostSummary(notification.post) }}</p> <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
</div> </div>
</template> </template>
<template v-if="notification.type == 'mention'"> <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"> <div class="text">
<p>%fa:at%{{ posterName }}</p> <p>%fa:at%{{ noteerName }}</p>
<p class="post-preview">{{ getPostSummary(notification.post) }}</p> <p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
</div> </div>
</template> </template>
@ -51,7 +51,7 @@
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<div class="text"> <div class="text">
<p>%fa:chart-pie%{{ name }}</p> <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> </div>
</template> </template>
</div> </div>
@ -59,7 +59,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; 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'; import getUserName from '../../../../../renderers/get-user-name';
export default Vue.extend({ export default Vue.extend({
@ -68,13 +68,13 @@ export default Vue.extend({
name() { name() {
return getUserName(this.notification.user); return getUserName(this.notification.user);
}, },
posterName() { noteerName() {
return getUserName(this.notification.post.user); return getUserName(this.notification.note.user);
} }
}, },
data() { data() {
return { return {
getPostSummary getNoteSummary
}; };
} }
}); });
@ -112,7 +112,7 @@ export default Vue.extend({
i, mk-reaction-icon i, mk-reaction-icon
margin-right 4px margin-right 4px
.post-ref .note-ref
[data-fa] [data-fa]
font-size 1em font-size 1em
@ -121,7 +121,7 @@ export default Vue.extend({
display inline-block display inline-block
margin-right 3px margin-right 3px
&.repost, &.quote &.renote, &.quote
.text p i .text p i
color #77B255 color #77B255

View File

@ -10,31 +10,31 @@
<mk-reaction-icon :reaction="notification.reaction"/> <mk-reaction-icon :reaction="notification.reaction"/>
<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
</p> </p>
<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post) }} %fa:quote-left%{{ getNoteSummary(notification.note) }}
%fa:quote-right% %fa:quote-right%
</router-link> </router-link>
</div> </div>
</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"/> <mk-time :time="notification.createdAt"/>
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
<div class="text"> <div class="text">
<p> <p>
%fa:retweet% %fa:retweet%
<router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link> <router-link :to="`/@${acct}`">{{ getUserName(notification.note.user) }}</router-link>
</p> </p>
<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% %fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%
</router-link> </router-link>
</div> </div>
</div> </div>
<template v-if="notification.type == 'quote'"> <template v-if="notification.type == 'quote'">
<mk-post :post="notification.post"/> <mk-note :note="notification.note"/>
</template> </template>
<div class="notification follow" v-if="notification.type == 'follow'"> <div class="notification follow" v-if="notification.type == 'follow'">
@ -51,11 +51,11 @@
</div> </div>
<template v-if="notification.type == 'reply'"> <template v-if="notification.type == 'reply'">
<mk-post :post="notification.post"/> <mk-note :note="notification.note"/>
</template> </template>
<template v-if="notification.type == 'mention'"> <template v-if="notification.type == 'mention'">
<mk-post :post="notification.post"/> <mk-note :note="notification.note"/>
</template> </template>
<div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> <div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
@ -68,8 +68,8 @@
%fa:chart-pie% %fa:chart-pie%
<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
</p> </p>
<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> <router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% %fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%
</router-link> </router-link>
</div> </div>
</div> </div>
@ -78,7 +78,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getPostSummary from '../../../../../renderers/get-post-summary'; import getNoteSummary from '../../../../../renderers/get-note-summary';
import getAcct from '../../../../../acct/render'; import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
@ -91,13 +91,13 @@ export default Vue.extend({
name() { name() {
return getUserName(this.notification.user); return getUserName(this.notification.user);
}, },
posterName() { noteerName() {
return getUserName(this.notification.post.user); return getUserName(this.notification.note.user);
} }
}, },
data() { data() {
return { return {
getPostSummary getNoteSummary
}; };
} }
}); });
@ -146,10 +146,10 @@ export default Vue.extend({
i, .mk-reaction-icon i, .mk-reaction-icon
margin-right 4px margin-right 4px
> .post-preview > .note-preview
color rgba(0, 0, 0, 0.7) color rgba(0, 0, 0, 0.7)
> .post-ref > .note-ref
color rgba(0, 0, 0, 0.7) color rgba(0, 0, 0, 0.7)
[data-fa] [data-fa]
@ -159,7 +159,7 @@ export default Vue.extend({
display inline-block display inline-block
margin-right 3px margin-right 3px
&.repost &.renote
.text p i .text p i
color #77B255 color #77B255

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-post-detail"> <div class="mk-note-detail">
<button <button
class="more" class="more"
v-if="p.reply && p.reply.replyId && context == null" v-if="p.reply && p.reply.replyId && context == null"
@ -10,21 +10,21 @@
<template v-if="contextFetching">%fa:spinner .pulse%</template> <template v-if="contextFetching">%fa:spinner .pulse%</template>
</button> </button>
<div class="context"> <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>
<div class="reply-to" v-if="p.reply"> <div class="reply-to" v-if="p.reply">
<x-sub :post="p.reply"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="repost" v-if="isRepost"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
%fa:retweet% %fa:retweet%
<router-link class="name" :to="`/@${acct}`"> <router-link class="name" :to="`/@${acct}`">
{{ name }} {{ name }}
</router-link> </router-link>
がRepost がRenote
</p> </p>
</div> </div>
<article> <article>
@ -38,33 +38,33 @@
</div> </div>
</header> </header>
<div class="body"> <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"> <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> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/> <mk-media-list :media-list="p.media"/>
</div> </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"/> <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> <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="map" v-if="p.geo" ref="map"></div>
<div class="repost" v-if="p.repost"> <div class="renote" v-if="p.renote">
<mk-post-preview :post="p.repost"/> <mk-note-preview :note="p.renote"/>
</div> </div>
</div> </div>
<router-link class="time" :to="`/@${pAcct}/${p.id}`"> <router-link class="time" :to="`/@${pAcct}/${p.id}`">
<mk-time :time="p.createdAt" mode="detail"/> <mk-time :time="p.createdAt" mode="detail"/>
</router-link> </router-link>
<footer> <footer>
<mk-reactions-viewer :post="p"/> <mk-reactions-viewer :note="p"/>
<button @click="reply" title="%i18n:mobile.tags.mk-post-detail.reply%"> <button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%">
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
</button> </button>
<button @click="repost" title="Repost"> <button @click="renote" title="Renote">
%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
</button> </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> %fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
</button> </button>
<button @click="menu" ref="menuButton"> <button @click="menu" ref="menuButton">
@ -73,7 +73,7 @@
</footer> </footer>
</article> </article>
<div class="replies" v-if="!compact"> <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>
</div> </div>
</template> </template>
@ -84,9 +84,9 @@ import getAcct from '../../../../../acct/render';
import getUserName from '../../../../../renderers/get-user-name'; import getUserName from '../../../../../renderers/get-user-name';
import parse from '../../../../../text/parse'; 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 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({ export default Vue.extend({
components: { components: {
@ -94,7 +94,7 @@ export default Vue.extend({
}, },
props: { props: {
post: { note: {
type: Object, type: Object,
required: true required: true
}, },
@ -113,10 +113,10 @@ export default Vue.extend({
computed: { computed: {
acct(): string { acct(): string {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name(): string { name(): string {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
pAcct(): string { pAcct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
@ -124,14 +124,14 @@ export default Vue.extend({
pName(): string { pName(): string {
return getUserName(this.p.user); return getUserName(this.p.user);
}, },
isRepost(): boolean { isRenote(): boolean {
return (this.post.repost && return (this.note.renote &&
this.post.text == null && this.note.text == null &&
this.post.mediaIds == null && this.note.mediaIds == null &&
this.post.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRepost ? this.post.repost : this.post; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
@ -155,8 +155,8 @@ export default Vue.extend({
mounted() { mounted() {
// Get replies // Get replies
if (!this.compact) { if (!this.compact) {
(this as any).api('posts/replies', { (this as any).api('notes/replies', {
postId: this.p.id, noteId: this.p.id,
limit: 8 limit: 8
}).then(replies => { }).then(replies => {
this.replies = replies; this.replies = replies;
@ -187,8 +187,8 @@ export default Vue.extend({
this.contextFetching = true; this.contextFetching = true;
// Fetch context // Fetch context
(this as any).api('posts/context', { (this as any).api('notes/context', {
postId: this.p.replyId noteId: this.p.replyId
}).then(context => { }).then(context => {
this.contextFetching = false; this.contextFetching = false;
this.context = context.reverse(); this.context = context.reverse();
@ -199,22 +199,22 @@ export default Vue.extend({
reply: this.p reply: this.p
}); });
}, },
repost() { renote() {
(this as any).apis.post({ (this as any).apis.post({
repost: this.p renote: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
post: this.p, note: this.p,
compact: true compact: true
}); });
}, },
menu() { menu() {
(this as any).os.new(MkPostMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
post: this.p, note: this.p,
compact: true compact: true
}); });
} }
@ -225,7 +225,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.mk-post-detail .mk-note-detail
overflow hidden overflow hidden
margin 0 auto margin 0 auto
padding 0 padding 0
@ -267,7 +267,7 @@ export default Vue.extend({
> * > *
border-bottom 1px solid #eef0f2 border-bottom 1px solid #eef0f2
> .repost > .renote
color #9dbb00 color #9dbb00
background linear-gradient(to bottom, #edfde2 0%, #fff 100%) background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
@ -357,10 +357,10 @@ export default Vue.extend({
> .body > .body
padding 8px 0 padding 8px 0
> .repost > .renote
margin 8px 0 margin 8px 0
> .mk-post-preview > .mk-note-preview
padding 16px padding 16px
border dashed 1px #c0dac6 border dashed 1px #c0dac6
border-radius 8px border-radius 8px

View File

@ -9,8 +9,8 @@
</div> </div>
</header> </header>
<div class="form"> <div class="form">
<mk-post-preview v-if="reply" :post="reply"/> <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.post-placeholder%'"></textarea> <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"> <div class="attaches" v-show="files.length != 0">
<x-draggable class="files" :list="files" :options="{ animation: 150 }"> <x-draggable class="files" :list="files" :options="{ animation: 150 }">
<div class="file" v-for="file in files" :key="file.id"> <div class="file" v-for="file in files" :key="file.id">
@ -112,7 +112,7 @@ export default Vue.extend({
post() { post() {
this.posting = true; this.posting = true;
const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== 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, text: this.text == '' ? undefined : this.text,
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
replyId: this.reply ? this.reply.id : undefined, replyId: this.reply ? this.reply.id : undefined,
@ -127,7 +127,7 @@ export default Vue.extend({
} : null, } : null,
viaMobile: viaMobile viaMobile: viaMobile
}).then(data => { }).then(data => {
this.$emit('post'); this.$emit('note');
this.$destroy(); this.$destroy();
}).catch(err => { }).catch(err => {
this.posting = false; this.posting = false;
@ -200,7 +200,7 @@ export default Vue.extend({
max-width 500px max-width 500px
margin 0 auto margin 0 auto
> .mk-post-preview > .mk-note-preview
padding 16px padding 16px
> .attaches > .attaches

View File

@ -1,19 +1,19 @@
<template> <template>
<div class="post" :class="{ repost: isRepost }"> <div class="note" :class="{ renote: isRenote }">
<div class="reply-to" v-if="p.reply"> <div class="reply-to" v-if="p.reply">
<x-sub :post="p.reply"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="repost" v-if="isRepost"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="`/@${acct}`"> <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> </router-link>
%fa:retweet% %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> <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> </p>
<mk-time :time="post.createdAt"/> <mk-time :time="note.createdAt"/>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="`/@${pAcct}`"> <router-link class="avatar-anchor" :to="`/@${pAcct}`">
@ -37,13 +37,13 @@
<a class="reply" v-if="p.reply"> <a class="reply" v-if="p.reply">
%fa:reply% %fa:reply%
</a> </a>
<mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> <mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.repost != null">RP:</a> <a class="rp" v-if="p.renote != null">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/> <mk-media-list :media-list="p.media"/>
</div> </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"> <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> <router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
</div> </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> <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="map" v-if="p.geo" ref="map"></div>
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
<div class="repost" v-if="p.repost"> <div class="renote" v-if="p.renote">
<mk-post-preview :post="p.repost"/> <mk-note-preview :note="p.renote"/>
</div> </div>
</div> </div>
<footer> <footer>
<mk-reactions-viewer :post="p" ref="reactionsViewer"/> <mk-reactions-viewer :note="p" ref="reactionsViewer"/>
<button @click="reply"> <button @click="reply">
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> %fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
</button> </button>
<button @click="repost" title="Repost"> <button @click="renote" title="Renote">
%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> %fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
</button> </button>
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton"> <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> %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 getUserName from '../../../../../renderers/get-user-name';
import parse from '../../../../../text/parse'; 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 MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './post.sub.vue'; import XSub from './note.sub.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XSub XSub
}, },
props: ['post'], props: ['note'],
data() { data() {
return { return {
@ -101,10 +101,10 @@ export default Vue.extend({
computed: { computed: {
acct(): string { acct(): string {
return getAcct(this.post.user); return getAcct(this.note.user);
}, },
name(): string { name(): string {
return getUserName(this.post.user); return getUserName(this.note.user);
}, },
pAcct(): string { pAcct(): string {
return getAcct(this.p.user); return getAcct(this.p.user);
@ -112,14 +112,14 @@ export default Vue.extend({
pName(): string { pName(): string {
return getUserName(this.p.user); return getUserName(this.p.user);
}, },
isRepost(): boolean { isRenote(): boolean {
return (this.post.repost && return (this.note.renote &&
this.post.text == null && this.note.text == null &&
this.post.mediaIds == null && this.note.mediaIds == null &&
this.post.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRepost ? this.post.repost : this.post; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
@ -192,7 +192,7 @@ export default Vue.extend({
type: 'capture', type: 'capture',
id: this.p.id 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) { decapture(withHandler = false) {
@ -201,18 +201,18 @@ export default Vue.extend({
type: 'decapture', type: 'decapture',
id: this.p.id id: this.p.id
}); });
if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
} }
}, },
onStreamConnected() { onStreamConnected() {
this.capture(); this.capture();
}, },
onStreamPostUpdated(data) { onStreamNoteUpdated(data) {
const post = data.post; const note = data.note;
if (post.id == this.post.id) { if (note.id == this.note.id) {
this.$emit('update:post', post); this.$emit('update:note', note);
} else if (post.id == this.post.repostId) { } else if (note.id == this.note.renoteId) {
this.post.repost = post; this.note.renote = note;
} }
}, },
reply() { reply() {
@ -220,22 +220,22 @@ export default Vue.extend({
reply: this.p reply: this.p
}); });
}, },
repost() { renote() {
(this as any).apis.post({ (this as any).apis.post({
repost: this.p renote: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
post: this.p, note: this.p,
compact: true compact: true
}); });
}, },
menu() { menu() {
(this as any).os.new(MkPostMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
post: this.p, note: this.p,
compact: true compact: true
}); });
} }
@ -246,14 +246,14 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.post .note
font-size 12px font-size 12px
border-bottom solid 1px #eaeaea border-bottom solid 1px #eaeaea
&:first-child &:first-child
border-radius 8px 8px 0 0 border-radius 8px 8px 0 0
> .repost > .renote
border-radius 8px 8px 0 0 border-radius 8px 8px 0 0
&:last-of-type &:last-of-type
@ -265,7 +265,7 @@ export default Vue.extend({
@media (min-width 500px) @media (min-width 500px)
font-size 16px font-size 16px
> .repost > .renote
color #9dbb00 color #9dbb00
background linear-gradient(to bottom, #edfde2 0%, #fff 100%) background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
@ -309,7 +309,7 @@ export default Vue.extend({
> .reply-to > .reply-to
background rgba(0, 0, 0, 0.0125) background rgba(0, 0, 0, 0.0125)
> .mk-post-preview > .mk-note-preview
background transparent background transparent
> article > article
@ -485,10 +485,10 @@ export default Vue.extend({
> .mk-poll > .mk-poll
font-size 80% font-size 80%
> .repost > .renote
margin 8px 0 margin 8px 0
> .mk-post-preview > .mk-note-preview
padding 16px padding 16px
border dashed 1px #c0dac6 border dashed 1px #c0dac6
border-radius 8px border-radius 8px

View File

@ -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>

View File

@ -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>

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="mk-timeline"> <div class="mk-timeline">
<mk-friends-maker v-if="alone"/> <mk-friends-maker v-if="alone"/>
<mk-posts :posts="posts"> <mk-notes :notes="notes">
<div class="init" v-if="fetching"> <div class="init" v-if="fetching">
%fa:spinner .pulse%%i18n:common.loading% %fa:spinner .pulse%%i18n:common.loading%
</div> </div>
<div class="empty" v-if="!fetching && posts.length == 0"> <div class="empty" v-if="!fetching && notes.length == 0">
%fa:R comments% %fa:R comments%
%i18n:mobile.tags.mk-home-timeline.empty-timeline% %i18n:mobile.tags.mk-home-timeline.empty-timeline%
</div> </div>
@ -13,7 +13,7 @@
<span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span> <span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span>
<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
</button> </button>
</mk-posts> </mk-notes>
</div> </div>
</template> </template>
@ -33,7 +33,7 @@ export default Vue.extend({
return { return {
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
posts: [], notes: [],
existMore: false, existMore: false,
connection: null, connection: null,
connectionId: null connectionId: null
@ -48,14 +48,14 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); 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('follow', this.onChangeFollowing);
this.connection.on('unfollow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing);
this.fetch(); this.fetch();
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('post', this.onPost); this.connection.off('note', this.onNote);
this.connection.off('follow', this.onChangeFollowing); this.connection.off('follow', this.onChangeFollowing);
this.connection.off('unfollow', this.onChangeFollowing); this.connection.off('unfollow', this.onChangeFollowing);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
@ -63,15 +63,15 @@ export default Vue.extend({
methods: { methods: {
fetch(cb?) { fetch(cb?) {
this.fetching = true; this.fetching = true;
(this as any).api('posts/timeline', { (this as any).api('notes/timeline', {
limit: limit + 1, limit: limit + 1,
untilDate: this.date ? (this.date as any).getTime() : undefined untilDate: this.date ? (this.date as any).getTime() : undefined
}).then(posts => { }).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} }
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
this.$emit('loaded'); this.$emit('loaded');
if (cb) cb(); if (cb) cb();
@ -79,22 +79,22 @@ export default Vue.extend({
}, },
more() { more() {
this.moreFetching = true; this.moreFetching = true;
(this as any).api('posts/timeline', { (this as any).api('notes/timeline', {
limit: limit + 1, limit: limit + 1,
untilId: this.posts[this.posts.length - 1].id untilId: this.notes[this.notes.length - 1].id
}).then(posts => { }).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} else { } else {
this.existMore = false; this.existMore = false;
} }
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
this.moreFetching = false; this.moreFetching = false;
}); });
}, },
onPost(post) { onNote(note) {
this.posts.unshift(post); this.notes.unshift(note);
}, },
onChangeFollowing() { onChangeFollowing() {
this.fetch(); this.fetch();

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="mk-user-timeline"> <div class="mk-user-timeline">
<mk-posts :posts="posts"> <mk-notes :notes="notes">
<div class="init" v-if="fetching"> <div class="init" v-if="fetching">
%fa:spinner .pulse%%i18n:common.loading% %fa:spinner .pulse%%i18n:common.loading%
</div> </div>
<div class="empty" v-if="!fetching && posts.length == 0"> <div class="empty" v-if="!fetching && notes.length == 0">
%fa:R comments% %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> </div>
<button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail"> <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:mobile.tags.mk-user-timeline.load-more%</span>
<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
</button> </button>
</mk-posts> </mk-notes>
</div> </div>
</template> </template>
@ -26,22 +26,22 @@ export default Vue.extend({
data() { data() {
return { return {
fetching: true, fetching: true,
posts: [], notes: [],
existMore: false, existMore: false,
moreFetching: false moreFetching: false
}; };
}, },
mounted() { mounted() {
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
withMedia: this.withMedia, withMedia: this.withMedia,
limit: limit + 1 limit: limit + 1
}).then(posts => { }).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} }
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
this.$emit('loaded'); this.$emit('loaded');
}); });
@ -49,19 +49,19 @@ export default Vue.extend({
methods: { methods: {
more() { more() {
this.moreFetching = true; this.moreFetching = true;
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
withMedia: this.withMedia, withMedia: this.withMedia,
limit: limit + 1, limit: limit + 1,
untilId: this.posts[this.posts.length - 1].id untilId: this.notes[this.notes.length - 1].id
}).then(posts => { }).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} else { } else {
this.existMore = false; this.existMore = false;
} }
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
this.moreFetching = false; this.moreFetching = false;
}); });
} }

View File

@ -64,7 +64,7 @@ import Vue from 'vue';
import * as XDraggable from 'vuedraggable'; import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import getPostSummary from '../../../../../renderers/get-post-summary'; import getNoteSummary from '../../../../../renderers/get-note-summary';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -124,14 +124,14 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); 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); this.connection.on('mobile_home_updated', this.onHomeUpdated);
document.addEventListener('visibilitychange', this.onVisibilitychange, false); document.addEventListener('visibilitychange', this.onVisibilitychange, false);
Progress.start(); Progress.start();
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('post', this.onStreamPost); this.connection.off('note', this.onStreamNote);
this.connection.off('mobile_home_updated', this.onHomeUpdated); this.connection.off('mobile_home_updated', this.onHomeUpdated);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
document.removeEventListener('visibilitychange', this.onVisibilitychange); document.removeEventListener('visibilitychange', this.onVisibilitychange);
@ -143,10 +143,10 @@ export default Vue.extend({
onLoaded() { onLoaded() {
Progress.done(); Progress.done();
}, },
onStreamPost(post) { onStreamNote(note) {
if (document.hidden && post.userId !== (this as any).os.i.id) { if (document.hidden && note.userId !== (this as any).os.i.id) {
this.unreadCount++; this.unreadCount++;
document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
} }
}, },
onVisibilitychange() { onVisibilitychange() {

View File

@ -1,12 +1,12 @@
<template> <template>
<mk-ui> <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"> <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> <div>
<mk-post-detail :post="post"/> <mk-note-detail :note="note"/>
</div> </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> </main>
</mk-ui> </mk-ui>
</template> </template>
@ -19,7 +19,7 @@ export default Vue.extend({
data() { data() {
return { return {
fetching: true, fetching: true,
post: null note: null
}; };
}, },
watch: { watch: {
@ -37,10 +37,10 @@ export default Vue.extend({
Progress.start(); Progress.start();
this.fetching = true; this.fetching = true;
(this as any).api('posts/show', { (this as any).api('notes/show', {
postId: this.$route.params.post noteId: this.$route.params.note
}).then(post => { }).then(note => {
this.post = post; this.note = note;
this.fetching = false; this.fetching = false;
Progress.done(); Progress.done();

View File

@ -2,13 +2,13 @@
<mk-ui> <mk-ui>
<span slot="header">%fa:search% {{ q }}</span> <span slot="header">%fa:search% {{ q }}</span>
<main v-if="!fetching"> <main v-if="!fetching">
<mk-posts :class="$style.posts" :posts="posts"> <mk-notes :class="$style.notes" :notes="notes">
<span v-if="posts.length == 0">{{ '%i18n:mobile.tags.mk-search-posts.empty%'.replace('{}', q) }}</span> <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"> <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:mobile.tags.mk-timeline.load-more%</span>
<span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span> <span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span>
</button> </button>
</mk-posts> </mk-notes>
</main> </main>
</mk-ui> </mk-ui>
</template> </template>
@ -25,7 +25,7 @@ export default Vue.extend({
return { return {
fetching: true, fetching: true,
existMore: false, existMore: false,
posts: [], notes: [],
offset: 0 offset: 0
}; };
}, },
@ -48,30 +48,30 @@ export default Vue.extend({
this.fetching = true; this.fetching = true;
Progress.start(); Progress.start();
(this as any).api('posts/search', Object.assign({ (this as any).api('notes/search', Object.assign({
limit: limit + 1 limit: limit + 1
}, parse(this.q))).then(posts => { }, parse(this.q))).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
this.existMore = true; this.existMore = true;
} }
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
Progress.done(); Progress.done();
}); });
}, },
more() { more() {
this.offset += limit; this.offset += limit;
(this as any).api('posts/search', Object.assign({ (this as any).api('notes/search', Object.assign({
limit: limit + 1, limit: limit + 1,
offset: this.offset offset: this.offset
}, parse(this.q))).then(posts => { }, parse(this.q))).then(notes => {
if (posts.length == limit + 1) { if (notes.length == limit + 1) {
posts.pop(); notes.pop();
} else { } else {
this.existMore = false; this.existMore = false;
} }
this.posts = this.posts.concat(posts); this.notes = this.notes.concat(notes);
}); });
} }
} }
@ -79,7 +79,7 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" module> <style lang="stylus" module>
.posts .notes
margin 8px auto margin 8px auto
max-width 500px max-width 500px
width calc(100% - 16px) width calc(100% - 16px)

View File

@ -27,8 +27,8 @@
</div> </div>
<div class="status"> <div class="status">
<a> <a>
<b>{{ user.postsCount | number }}</b> <b>{{ user.notesCount | number }}</b>
<i>%i18n:mobile.tags.mk-user.posts%</i> <i>%i18n:mobile.tags.mk-user.notes%</i>
</a> </a>
<a :href="`@${acct}/following`"> <a :href="`@${acct}/following`">
<b>{{ user.followingCount | number }}</b> <b>{{ user.followingCount | number }}</b>
@ -44,13 +44,13 @@
<nav> <nav>
<div class="nav-container"> <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 == '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> <a :data-is-active=" page == 'media' " @click="page = 'media'">%i18n:mobile.tags.mk-user.media%</a>
</div> </div>
</nav> </nav>
<div class="body"> <div class="body">
<x-home v-if="page == 'home'" :user="user"/> <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/> <mk-user-timeline v-if="page == 'media'" :user="user" with-media/>
</div> </div>
</main> </main>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="root posts"> <div class="root notes">
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p> <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 && posts.length > 0"> <div v-if="!fetching && notes.length > 0">
<mk-post-card v-for="post in posts" :key="post.id" :post="post"/> <mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
</div> </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> </div>
</template> </template>
@ -15,14 +15,14 @@ export default Vue.extend({
data() { data() {
return { return {
fetching: true, fetching: true,
posts: [] notes: []
}; };
}, },
mounted() { mounted() {
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id userId: this.user.id
}).then(posts => { }).then(notes => {
this.posts = posts; this.notes = notes;
this.fetching = false; this.fetching = false;
}); });
} }
@ -30,7 +30,7 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.root.posts .root.notes
> div > div
overflow-x scroll overflow-x scroll

View File

@ -5,7 +5,7 @@
<a v-for="image in images" <a v-for="image in images"
class="img" class="img"
:style="`background-image: url(${image.media.url}?thumbnail&size=256)`" :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> ></a>
</div> </div>
<p class="empty" v-if="!fetching && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p> <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 getAcct
}, },
mounted() { mounted() {
(this as any).api('users/posts', { (this as any).api('users/notes', {
userId: this.user.id, userId: this.user.id,
withMedia: true, withMedia: true,
limit: 6 limit: 6
}).then(posts => { }).then(notes => {
posts.forEach(post => { notes.forEach(note => {
post.media.forEach(media => { note.media.forEach(media => {
if (this.images.length < 9) this.images.push({ if (this.images.length < 9) this.images.push({
post, note,
media media
}); });
}); });

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="root home"> <div class="root home">
<mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/> <mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
<section class="recent-posts"> <section class="recent-notes">
<h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> <h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-notes%</h2>
<div> <div>
<x-posts :user="user"/> <x-notes :user="user"/>
</div> </div>
</section> </section>
<section class="images"> <section class="images">
@ -37,14 +37,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XPosts from './home.posts.vue'; import XNotes from './home.notes.vue';
import XPhotos from './home.photos.vue'; import XPhotos from './home.photos.vue';
import XFriends from './home.friends.vue'; import XFriends from './home.friends.vue';
import XFollowersYouKnow from './home.followers-you-know.vue'; import XFollowersYouKnow from './home.followers-you-know.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XPosts, XNotes,
XPhotos, XPhotos,
XFriends, XFriends,
XFollowersYouKnow XFollowersYouKnow
@ -58,7 +58,7 @@ export default Vue.extend({
max-width 600px max-width 600px
margin 0 auto margin 0 auto
> .mk-post-detail > .mk-note-detail
margin 0 0 8px 0 margin 0 0 8px 0
> section > section

View File

@ -2,7 +2,7 @@
<h1>Misskey<i>Statistics</i></h1> <h1>Misskey<i>Statistics</i></h1>
<main v-if="!initializing"> <main v-if="!initializing">
<mk-users stats={ stats }/> <mk-users stats={ stats }/>
<mk-posts stats={ stats }/> <mk-notes stats={ stats }/>
</main> </main>
<footer><a href={ _URL_ }>{ _HOST_ }</a></footer> <footer><a href={ _URL_ }>{ _HOST_ }</a></footer>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@ -56,9 +56,9 @@
</script> </script>
</mk-index> </mk-index>
<mk-posts> <mk-notes>
<h2>%i18n:stats.posts-count% <b>{ stats.postsCount }</b></h2> <h2>%i18n:stats.notes-count% <b>{ stats.notesCount }</b></h2>
<mk-posts-chart v-if="!initializing" data={ data }/> <mk-notes-chart v-if="!initializing" data={ data }/>
<style lang="stylus" scoped> <style lang="stylus" scoped>
:scope :scope
display block display block
@ -70,7 +70,7 @@
this.stats = this.opts.stats; this.stats = this.opts.stats;
this.on('mount', () => { this.on('mount', () => {
this.$root.$data.os.api('aggregation/posts', { this.$root.$data.os.api('aggregation/notes', {
limit: 365 limit: 365
}).then(data => { }).then(data => {
this.update({ this.update({
@ -80,7 +80,7 @@
}); });
}); });
</script> </script>
</mk-posts> </mk-notes>
<mk-users> <mk-users>
<h2>%i18n:stats.users-count% <b>{ stats.usersCount }</b></h2> <h2>%i18n:stats.users-count% <b>{ stats.usersCount }</b></h2>
@ -108,11 +108,11 @@
</script> </script>
</mk-users> </mk-users>
<mk-posts-chart> <mk-notes-chart>
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> <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 <polyline
riot-points={ pointsPost } riot-points={ pointsNote }
fill="none" fill="none"
stroke-width="1" stroke-width="1"
stroke="#41ddde"/> stroke="#41ddde"/>
@ -122,7 +122,7 @@
stroke-width="1" stroke-width="1"
stroke="#f7796c"/> stroke="#f7796c"/>
<polyline <polyline
riot-points={ pointsRepost } riot-points={ pointsRenote }
fill="none" fill="none"
stroke-width="1" stroke-width="1"
stroke="#a1de41"/> stroke="#a1de41"/>
@ -147,7 +147,7 @@
this.viewBoxY = 80; this.viewBoxY = 80;
this.data = this.opts.data.reverse(); 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)); const peak = Math.max.apply(null, this.data.map(d => d.total));
this.on('mount', () => { this.on('mount', () => {
@ -156,14 +156,14 @@
this.render = () => { this.render = () => {
this.update({ 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(' '), 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(' ') pointsTotal: this.data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ')
}); });
}; };
</script> </script>
</mk-posts-chart> </mk-notes-chart>
<mk-users-chart> <mk-users-chart>
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">

View File

@ -1,8 +1,8 @@
endpoint: "posts/create" endpoint: "notes/create"
desc: desc:
ja: "投稿します。" ja: "投稿します。"
en: "Compose new post." en: "Compose new note."
params: params:
- name: "text" - name: "text"
@ -10,7 +10,7 @@ params:
optional: true optional: true
desc: desc:
ja: "投稿の本文" ja: "投稿の本文"
en: "The text of your post" en: "The text of your note"
- name: "cw" - name: "cw"
type: "string" type: "string"
optional: true optional: true
@ -24,17 +24,17 @@ params:
ja: "添付するメディア(1~4つ)" ja: "添付するメディア(1~4つ)"
en: "Media you want to attach (1~4)" en: "Media you want to attach (1~4)"
- name: "replyId" - name: "replyId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "返信する投稿" ja: "返信する投稿"
en: "The post you want to reply" en: "The note you want to reply"
- name: "repostId" - name: "renoteId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "引用する投稿" ja: "引用する投稿"
en: "The post you want to quote" en: "The note you want to quote"
- name: "poll" - name: "poll"
type: "object" type: "object"
optional: true optional: true
@ -51,9 +51,9 @@ params:
en: "Choices of a poll" en: "Choices of a poll"
res: res:
- name: "createdPost" - name: "createdNote"
type: "entity(Post)" type: "entity(Note)"
optional: false optional: false
desc: desc:
ja: "作成した投稿" ja: "作成した投稿"
en: "A post that created" en: "A note that created"

View File

@ -1,4 +1,4 @@
endpoint: "posts/timeline" endpoint: "notes/timeline"
desc: desc:
ja: "タイムラインを取得します。" ja: "タイムラインを取得します。"
@ -11,12 +11,12 @@ params:
desc: desc:
ja: "取得する最大の数" ja: "取得する最大の数"
- name: "sinceId" - name: "sinceId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "指定すると、この投稿を基点としてより新しい投稿を取得します" ja: "指定すると、この投稿を基点としてより新しい投稿を取得します"
- name: "untilId" - name: "untilId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "指定すると、この投稿を基点としてより古い投稿を取得します" ja: "指定すると、この投稿を基点としてより古い投稿を取得します"

View File

@ -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: "速度。メートル / 秒数で表す。"

View File

@ -1,8 +1,8 @@
name: "Post" name: "Note"
desc: desc:
ja: "投稿。" ja: "投稿。"
en: "A post." en: "A note."
props: props:
- name: "id" - name: "id"
@ -10,31 +10,31 @@ props:
optional: false optional: false
desc: desc:
ja: "投稿ID" ja: "投稿ID"
en: "The ID of this post" en: "The ID of this note"
- name: "createdAt" - name: "createdAt"
type: "date" type: "date"
optional: false optional: false
desc: desc:
ja: "投稿日時" ja: "投稿日時"
en: "The posted date of this post" en: "The posted date of this note"
- name: "viaMobile" - name: "viaMobile"
type: "boolean" type: "boolean"
optional: true optional: true
desc: desc:
ja: "モバイル端末から投稿したか否か(自己申告であることに留意)" ja: "モバイル端末から投稿したか否か(自己申告であることに留意)"
en: "Whether this post sent via a mobile device" en: "Whether this note sent via a mobile device"
- name: "text" - name: "text"
type: "string" type: "string"
optional: true optional: true
desc: desc:
ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" 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" - name: "textHtml"
type: "string" type: "string"
optional: true optional: true
desc: desc:
ja: "投稿の本文 (HTML) (投稿時は無視)" 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" - name: "mediaIds"
type: "id(DriveFile)[]" type: "id(DriveFile)[]"
optional: true optional: true
@ -52,48 +52,48 @@ props:
optional: false optional: false
desc: desc:
ja: "投稿者ID" ja: "投稿者ID"
en: "The ID of author of this post" en: "The ID of author of this note"
- name: "user" - name: "user"
type: "entity(User)" type: "entity(User)"
optional: true optional: true
desc: desc:
ja: "投稿者" ja: "投稿者"
en: "The author of this post" en: "The author of this note"
- name: "myReaction" - name: "myReaction"
type: "string" type: "string"
optional: true optional: true
desc: desc:
ja: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>" 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" - name: "reactionCounts"
type: "object" type: "object"
optional: false optional: false
desc: desc:
ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト" ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト"
- name: "replyId" - name: "replyId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "返信した投稿のID" ja: "返信した投稿のID"
en: "The ID of the replyed post" en: "The ID of the replyed note"
- name: "reply" - name: "reply"
type: "entity(Post)" type: "entity(Note)"
optional: true optional: true
desc: desc:
ja: "返信した投稿" ja: "返信した投稿"
en: "The replyed post" en: "The replyed note"
- name: "repostId" - name: "renoteId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "引用した投稿のID" ja: "引用した投稿のID"
en: "The ID of the quoted post" en: "The ID of the quoted note"
- name: "repost" - name: "renote"
type: "entity(Post)" type: "entity(Note)"
optional: true optional: true
desc: desc:
ja: "引用した投稿" ja: "引用した投稿"
en: "The quoted post" en: "The quoted note"
- name: "poll" - name: "poll"
type: "object" type: "object"
optional: true optional: true

View File

@ -81,24 +81,24 @@ props:
desc: desc:
ja: "自分がこのユーザーをミュートしているか" ja: "自分がこのユーザーをミュートしているか"
en: "Whether you muted this user" en: "Whether you muted this user"
- name: "postsCount" - name: "notesCount"
type: "number" type: "number"
optional: false optional: false
desc: desc:
ja: "投稿の数" ja: "投稿の数"
en: "The number of the posts of this user" en: "The number of the notes of this user"
- name: "pinnedPost" - name: "pinnedNote"
type: "entity(Post)" type: "entity(Note)"
optional: true optional: true
desc: desc:
ja: "ピン留めされた投稿" ja: "ピン留めされた投稿"
en: "The pinned post of this user" en: "The pinned note of this user"
- name: "pinnedPostId" - name: "pinnedNoteId"
type: "id(Post)" type: "id(Note)"
optional: true optional: true
desc: desc:
ja: "ピン留めされた投稿のID" ja: "ピン留めされた投稿のID"
en: "The ID of the pinned post of this user" en: "The ID of the pinned note of this user"
- name: "driveCapacity" - name: "driveCapacity"
type: "number" type: "number"
optional: false optional: false

View File

@ -4,7 +4,7 @@ p ユーザーページから、そのユーザーをミュートすることが
p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります: p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります:
ul ul
li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRepost) li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote)
li そのユーザーからの通知 li そのユーザーからの通知
li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴 li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴

View File

@ -64,19 +64,19 @@ section
tr tr
td mute td mute
td td
| mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostを除外する(デフォルト) | mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteを除外する(デフォルト)
br br
| mute_related ... ミュートしているユーザーの投稿に対する返信やRepostだけ除外する | mute_related ... ミュートしているユーザーの投稿に対する返信やRenoteだけ除外する
br br
| mute_direct ... ミュートしているユーザーの投稿だけ除外する | mute_direct ... ミュートしているユーザーの投稿だけ除外する
br br
| disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostも含める | disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteも含める
br br
| direct_only ... ミュートしているユーザーの投稿だけに限定 | direct_only ... ミュートしているユーザーの投稿だけに限定
br br
| related_only ... ミュートしているユーザーの投稿に対する返信やRepostだけに限定 | related_only ... ミュートしているユーザーの投稿に対する返信やRenoteだけに限定
br br
| all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostに限定 | all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteに限定
tr tr
td reply td reply
td td
@ -86,11 +86,11 @@ section
br br
| null ... 特に限定しない(デフォルト) | null ... 特に限定しない(デフォルト)
tr tr
td repost td renote
td td
| true ... Repostに限定。 | true ... Renoteに限定。
br br
| false ... Repostでない投稿に限定。 | false ... Renoteでない投稿に限定。
br br
| null ... 特に限定しない(デフォルト) | null ... 特に限定しない(デフォルト)
tr tr

View File

@ -8,5 +8,5 @@ export type IFavorite = {
_id: mongo.ObjectID; _id: mongo.ObjectID;
createdAt: Date; createdAt: Date;
userId: mongo.ObjectID; userId: mongo.ObjectID;
postId: mongo.ObjectID; noteId: mongo.ObjectID;
}; };

View File

@ -1,17 +1,17 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import deepcopy = require('deepcopy'); import deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import Reaction from './post-reaction'; import Reaction from './note-reaction';
import { pack as packUser } from './user'; import { pack as packUser } from './user';
const PostReaction = db.get<IPostReaction>('postReactions'); const NoteReaction = db.get<INoteReaction>('noteReactions');
PostReaction.createIndex(['userId', 'postId'], { unique: true }); NoteReaction.createIndex(['userId', 'noteId'], { unique: true });
export default PostReaction; export default NoteReaction;
export interface IPostReaction { export interface INoteReaction {
_id: mongo.ObjectID; _id: mongo.ObjectID;
createdAt: Date; createdAt: Date;
postId: mongo.ObjectID; noteId: mongo.ObjectID;
userId: mongo.ObjectID; userId: mongo.ObjectID;
reaction: string; reaction: string;
} }

View File

@ -0,0 +1,13 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
const NoteWatching = db.get<INoteWatching>('noteWatching');
NoteWatching.createIndex(['userId', 'noteId'], { unique: true });
export default NoteWatching;
export interface INoteWatching {
_id: mongo.ObjectID;
createdAt: Date;
userId: mongo.ObjectID;
noteId: mongo.ObjectID;
}

View File

@ -6,14 +6,14 @@ import { IUser, pack as packUser } from './user';
import { pack as packApp } from './app'; import { pack as packApp } from './app';
import { pack as packChannel } from './channel'; import { pack as packChannel } from './channel';
import Vote from './poll-vote'; import Vote from './poll-vote';
import Reaction from './post-reaction'; import Reaction from './note-reaction';
import { pack as packFile } from './drive-file'; import { pack as packFile } from './drive-file';
const Post = db.get<IPost>('posts'); const Note = db.get<INote>('notes');
Post.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('uri', { sparse: true, unique: true });
export default Post; export default Note;
export function isValidText(text: string): boolean { export function isValidText(text: string): boolean {
return text.length <= 1000 && text.trim() != ''; return text.length <= 1000 && text.trim() != '';
@ -23,14 +23,14 @@ export function isValidCw(text: string): boolean {
return text.length <= 100 && text.trim() != ''; return text.length <= 100 && text.trim() != '';
} }
export type IPost = { export type INote = {
_id: mongo.ObjectID; _id: mongo.ObjectID;
channelId: mongo.ObjectID; channelId: mongo.ObjectID;
createdAt: Date; createdAt: Date;
deletedAt: Date; deletedAt: Date;
mediaIds: mongo.ObjectID[]; mediaIds: mongo.ObjectID[];
replyId: mongo.ObjectID; replyId: mongo.ObjectID;
repostId: mongo.ObjectID; renoteId: mongo.ObjectID;
poll: any; // todo poll: any; // todo
text: string; text: string;
tags: string[]; tags: string[];
@ -39,7 +39,7 @@ export type IPost = {
userId: mongo.ObjectID; userId: mongo.ObjectID;
appId: mongo.ObjectID; appId: mongo.ObjectID;
viaMobile: boolean; viaMobile: boolean;
repostCount: number; renoteCount: number;
repliesCount: number; repliesCount: number;
reactionCounts: any; reactionCounts: any;
mentions: mongo.ObjectID[]; mentions: mongo.ObjectID[];
@ -57,7 +57,7 @@ export type IPost = {
_reply?: { _reply?: {
userId: mongo.ObjectID; userId: mongo.ObjectID;
}; };
_repost?: { _renote?: {
userId: mongo.ObjectID; userId: mongo.ObjectID;
}; };
_user: { _user: {
@ -70,15 +70,15 @@ export type IPost = {
}; };
/** /**
* Pack a post for API response * Pack a note for API response
* *
* @param post target * @param note target
* @param me? serializee * @param me? serializee
* @param options? serialize options * @param options? serialize options
* @return response * @return response
*/ */
export const pack = async ( export const pack = async (
post: string | mongo.ObjectID | IPost, note: string | mongo.ObjectID | INote,
me?: string | mongo.ObjectID | IUser, me?: string | mongo.ObjectID | IUser,
options?: { options?: {
detail: boolean detail: boolean
@ -97,58 +97,58 @@ export const pack = async (
: (me as IUser)._id : (me as IUser)._id
: null; : null;
let _post: any; let _note: any;
// Populate the post if 'post' is ID // Populate the note if 'note' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(post)) { if (mongo.ObjectID.prototype.isPrototypeOf(note)) {
_post = await Post.findOne({ _note = await Note.findOne({
_id: post _id: note
}); });
} else if (typeof post === 'string') { } else if (typeof note === 'string') {
_post = await Post.findOne({ _note = await Note.findOne({
_id: new mongo.ObjectID(post) _id: new mongo.ObjectID(note)
}); });
} else { } else {
_post = deepcopy(post); _note = deepcopy(note);
} }
if (!_post) throw 'invalid post arg.'; if (!_note) throw 'invalid note arg.';
const id = _post._id; const id = _note._id;
// Rename _id to id // Rename _id to id
_post.id = _post._id; _note.id = _note._id;
delete _post._id; delete _note._id;
delete _post.mentions; delete _note.mentions;
if (_post.geo) delete _post.geo.type; if (_note.geo) delete _note.geo.type;
// Populate user // Populate user
_post.user = packUser(_post.userId, meId); _note.user = packUser(_note.userId, meId);
// Populate app // Populate app
if (_post.appId) { if (_note.appId) {
_post.app = packApp(_post.appId); _note.app = packApp(_note.appId);
} }
// Populate channel // Populate channel
if (_post.channelId) { if (_note.channelId) {
_post.channel = packChannel(_post.channelId); _note.channel = packChannel(_note.channelId);
} }
// Populate media // Populate media
if (_post.mediaIds) { if (_note.mediaIds) {
_post.media = Promise.all(_post.mediaIds.map(fileId => _note.media = Promise.all(_note.mediaIds.map(fileId =>
packFile(fileId) packFile(fileId)
)); ));
} }
// When requested a detailed post data // When requested a detailed note data
if (opts.detail) { if (opts.detail) {
// Get previous post info // Get previous note info
_post.prev = (async () => { _note.prev = (async () => {
const prev = await Post.findOne({ const prev = await Note.findOne({
userId: _post.userId, userId: _note.userId,
_id: { _id: {
$lt: id $lt: id
} }
@ -163,10 +163,10 @@ export const pack = async (
return prev ? prev._id : null; return prev ? prev._id : null;
})(); })();
// Get next post info // Get next note info
_post.next = (async () => { _note.next = (async () => {
const next = await Post.findOne({ const next = await Note.findOne({
userId: _post.userId, userId: _note.userId,
_id: { _id: {
$gt: id $gt: id
} }
@ -181,27 +181,27 @@ export const pack = async (
return next ? next._id : null; return next ? next._id : null;
})(); })();
if (_post.replyId) { if (_note.replyId) {
// Populate reply to post // Populate reply to note
_post.reply = pack(_post.replyId, meId, { _note.reply = pack(_note.replyId, meId, {
detail: false detail: false
}); });
} }
if (_post.repostId) { if (_note.renoteId) {
// Populate repost // Populate renote
_post.repost = pack(_post.repostId, meId, { _note.renote = pack(_note.renoteId, meId, {
detail: _post.text == null detail: _note.text == null
}); });
} }
// Poll // Poll
if (meId && _post.poll) { if (meId && _note.poll) {
_post.poll = (async (poll) => { _note.poll = (async (poll) => {
const vote = await Vote const vote = await Vote
.findOne({ .findOne({
userId: meId, userId: meId,
postId: id noteId: id
}); });
if (vote != null) { if (vote != null) {
@ -212,16 +212,16 @@ export const pack = async (
} }
return poll; return poll;
})(_post.poll); })(_note.poll);
} }
// Fetch my reaction // Fetch my reaction
if (meId) { if (meId) {
_post.myReaction = (async () => { _note.myReaction = (async () => {
const reaction = await Reaction const reaction = await Reaction
.findOne({ .findOne({
userId: meId, userId: meId,
postId: id, noteId: id,
deletedAt: { $exists: false } deletedAt: { $exists: false }
}); });
@ -234,8 +234,8 @@ export const pack = async (
} }
} }
// resolve promises in _post object // resolve promises in _note object
_post = await rap(_post); _note = await rap(_note);
return _post; return _note;
}; };

View File

@ -2,7 +2,7 @@ import * as mongo from 'mongodb';
import deepcopy = require('deepcopy'); import deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import { IUser, pack as packUser } from './user'; import { IUser, pack as packUser } from './user';
import { pack as packPost } from './post'; import { pack as packNote } from './note';
const Notification = db.get<INotification>('notifications'); const Notification = db.get<INotification>('notifications');
export default Notification; export default Notification;
@ -36,12 +36,12 @@ export interface INotification {
* follow - * follow -
* mention - 稿 * mention - 稿
* reply - (Watchしている)稿 * reply - (Watchしている)稿
* repost - (Watchしている)稿Repostされた * renote - (Watchしている)稿Renoteされた
* quote - (Watchしている)稿Repostされた * quote - (Watchしている)稿Renoteされた
* reaction - (Watchしている)稿 * reaction - (Watchしている)稿
* poll_vote - (Watchしている)稿 * poll_vote - (Watchしている)稿
*/ */
type: 'follow' | 'mention' | 'reply' | 'repost' | 'quote' | 'reaction' | 'poll_vote'; type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'poll_vote';
/** /**
* *
@ -91,12 +91,12 @@ export const pack = (notification: any) => new Promise<any>(async (resolve, reje
break; break;
case 'mention': case 'mention':
case 'reply': case 'reply':
case 'repost': case 'renote':
case 'quote': case 'quote':
case 'reaction': case 'reaction':
case 'poll_vote': case 'poll_vote':
// Populate post // Populate note
_notification.post = await packPost(_notification.postId, me); _notification.note = await packNote(_notification.noteId, me);
break; break;
default: default:
console.error(`Unknown type: ${_notification.type}`); console.error(`Unknown type: ${_notification.type}`);

View File

@ -8,6 +8,6 @@ export interface IPollVote {
_id: mongo.ObjectID; _id: mongo.ObjectID;
createdAt: Date; createdAt: Date;
userId: mongo.ObjectID; userId: mongo.ObjectID;
postId: mongo.ObjectID; noteId: mongo.ObjectID;
choice: number; choice: number;
} }

View File

@ -1,13 +0,0 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
const PostWatching = db.get<IPostWatching>('postWatching');
PostWatching.createIndex(['userId', 'postId'], { unique: true });
export default PostWatching;
export interface IPostWatching {
_id: mongo.ObjectID;
createdAt: Date;
userId: mongo.ObjectID;
postId: mongo.ObjectID;
}

View File

@ -2,7 +2,7 @@ import * as mongo from 'mongodb';
import deepcopy = require('deepcopy'); import deepcopy = require('deepcopy');
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import db from '../db/mongodb'; import db from '../db/mongodb';
import { IPost, pack as packPost } from './post'; import { INote, pack as packNote } from './note';
import Following from './following'; import Following from './following';
import Mute from './mute'; import Mute from './mute';
import getFriends from '../server/api/common/get-friends'; import getFriends from '../server/api/common/get-friends';
@ -22,7 +22,7 @@ type IUserBase = {
followersCount: number; followersCount: number;
followingCount: number; followingCount: number;
name?: string; name?: string;
postsCount: number; notesCount: number;
driveCapacity: number; driveCapacity: number;
username: string; username: string;
usernameLower: string; usernameLower: string;
@ -30,8 +30,8 @@ type IUserBase = {
bannerId: mongo.ObjectID; bannerId: mongo.ObjectID;
data: any; data: any;
description: string; description: string;
latestPost: IPost; latestNote: INote;
pinnedPostId: mongo.ObjectID; pinnedNoteId: mongo.ObjectID;
isSuspended: boolean; isSuspended: boolean;
keywords: string[]; keywords: string[];
host: string; host: string;
@ -120,7 +120,7 @@ export function init(user): IUser {
user._id = new mongo.ObjectID(user._id); user._id = new mongo.ObjectID(user._id);
user.avatarId = new mongo.ObjectID(user.avatarId); user.avatarId = new mongo.ObjectID(user.avatarId);
user.bannerId = new mongo.ObjectID(user.bannerId); user.bannerId = new mongo.ObjectID(user.bannerId);
user.pinnedPostId = new mongo.ObjectID(user.pinnedPostId); user.pinnedNoteId = new mongo.ObjectID(user.pinnedNoteId);
return user; return user;
} }
@ -186,7 +186,7 @@ export const pack = (
delete _user._id; delete _user._id;
// Remove needless properties // Remove needless properties
delete _user.latestPost; delete _user.latestNote;
if (!_user.host) { if (!_user.host) {
// Remove private properties // Remove private properties
@ -260,9 +260,9 @@ export const pack = (
} }
if (opts.detail) { if (opts.detail) {
if (_user.pinnedPostId) { if (_user.pinnedNoteId) {
// Populate pinned post // Populate pinned note
_user.pinnedPost = packPost(_user.pinnedPostId, meId, { _user.pinnedNote = packNote(_user.pinnedNoteId, meId, {
detail: true detail: true
}); });
} }

View File

@ -24,7 +24,7 @@ const id = conf.othello_ai.id;
*/ */
const i = conf.othello_ai.i; const i = conf.othello_ai.i;
let post; let note;
process.on('message', async msg => { process.on('message', async msg => {
// 親プロセスからデータをもらう // 親プロセスからデータをもらう
@ -51,13 +51,13 @@ process.on('message', async msg => {
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんの接待を始めました!` ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんの接待を始めました!`
: `対局を?[${getUserName(user)}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`; : `対局を?[${getUserName(user)}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`;
const res = await request.post(`${conf.api_url}/posts/create`, { const res = await request.post(`${conf.api_url}/notes/create`, {
json: { i, json: { i,
text: `${text}\n→[観戦する](${url})` text: `${text}\n→[観戦する](${url})`
} }
}); });
post = res.createdPost; note = res.createdNote;
//#endregion //#endregion
} }
@ -83,9 +83,9 @@ process.on('message', async msg => {
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに勝ちました♪` ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに勝ちました♪`
: `?[${getUserName(user)}](${conf.url}/@${user.username})さんに負けました...`; : `?[${getUserName(user)}](${conf.url}/@${user.username})さんに負けました...`;
await request.post(`${conf.api_url}/posts/create`, { await request.post(`${conf.api_url}/notes/create`, {
json: { i, json: { i,
repostId: post.id, renoteId: note.id,
text: text text: text
} }
}); });

View File

@ -46,28 +46,28 @@ homeStream.on('message', message => {
// タイムライン上でなんか言われたまたは返信されたとき // タイムライン上でなんか言われたまたは返信されたとき
if (msg.type == 'mention' || msg.type == 'reply') { if (msg.type == 'mention' || msg.type == 'reply') {
const post = msg.body; const note = msg.body;
if (post.userId == id) return; if (note.userId == id) return;
// リアクションする // リアクションする
request.post(`${conf.api_url}/posts/reactions/create`, { request.post(`${conf.api_url}/notes/reactions/create`, {
json: { i, json: { i,
postId: post.id, noteId: note.id,
reaction: 'love' reaction: 'love'
} }
}); });
if (post.text) { if (note.text) {
if (post.text.indexOf('オセロ') > -1) { if (note.text.indexOf('オセロ') > -1) {
request.post(`${conf.api_url}/posts/create`, { request.post(`${conf.api_url}/notes/create`, {
json: { i, json: { i,
replyId: post.id, replyId: note.id,
text: '良いですよ~' text: '良いですよ~'
} }
}); });
invite(post.userId); invite(note.userId);
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More