parent
							
								
									c7106d250c
								
							
						
					
					
						commit
						a1b490afa7
					
				
					 167 changed files with 4440 additions and 1762 deletions
				
			
		|  | @ -33,7 +33,7 @@ common: | |||
|     confused: "Confused" | ||||
|     pudding: "Pudding" | ||||
| 
 | ||||
|   post_categories: | ||||
|   note_categories: | ||||
|     music: "Music" | ||||
|     game: "Video 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 category" | ||||
|  | @ -211,7 +211,7 @@ ch: | |||
|       textarea: "Write here" | ||||
|       upload: "Upload" | ||||
|       drive: "Drive" | ||||
|       post: "Do" | ||||
|       note: "Do" | ||||
|       posting: "Doing" | ||||
| 
 | ||||
| desktop: | ||||
|  | @ -304,8 +304,8 @@ desktop: | |||
|       settings: "Settings" | ||||
|       signout: "Sign out" | ||||
| 
 | ||||
|     mk-ui-header-post-button: | ||||
|       post: "Compose new Post" | ||||
|     mk-ui-header-note-button: | ||||
|       note: "Compose new Post" | ||||
| 
 | ||||
|     mk-ui-header-notifications: | ||||
|       title: "Notifications" | ||||
|  | @ -350,18 +350,18 @@ desktop: | |||
|       no-users: "No muted users" | ||||
| 
 | ||||
|     mk-post-form: | ||||
|       post-placeholder: "What's happening?" | ||||
|       reply-placeholder: "Reply to this post..." | ||||
|       quote-placeholder: "Quote this post..." | ||||
|       post: "Post" | ||||
|       note-placeholder: "What's happening?" | ||||
|       reply-placeholder: "Reply to this note..." | ||||
|       quote-placeholder: "Quote this note..." | ||||
|       note: "Post" | ||||
|       reply: "Reply" | ||||
|       repost: "Repost" | ||||
|       renote: "Renote" | ||||
|       posted: "Posted!" | ||||
|       replied: "Replied!" | ||||
|       reposted: "Reposted!" | ||||
|       post-failed: "Failed to post" | ||||
|       note-failed: "Failed to note" | ||||
|       reply-failed: "Failed to reply" | ||||
|       repost-failed: "Failed to repost" | ||||
|       renote-failed: "Failed to renote" | ||||
|       posting: "Posting" | ||||
|       attach-media-from-local: "Attach media from your pc" | ||||
|       attach-media-from-drive: "Attach media from the drive" | ||||
|  | @ -371,14 +371,14 @@ desktop: | |||
|       text-remain: "{} chars remaining" | ||||
| 
 | ||||
|     mk-post-form-window: | ||||
|       post: "New post" | ||||
|       note: "New note" | ||||
|       reply: "Reply" | ||||
|       attaches: "{} media attached" | ||||
|       uploading-media: "Uploading {} media" | ||||
| 
 | ||||
|     mk-post-page: | ||||
|       prev: "Previous post" | ||||
|       next: "Next post" | ||||
|     mk-note-page: | ||||
|       prev: "Previous note" | ||||
|       next: "Next note" | ||||
| 
 | ||||
|     mk-settings: | ||||
|       profile: "Profile" | ||||
|  | @ -390,10 +390,10 @@ desktop: | |||
|       other: "Other" | ||||
|       license: "License" | ||||
| 
 | ||||
|     mk-timeline-post: | ||||
|     mk-timeline-note: | ||||
|       reposted-by: "Reposted by {}" | ||||
|       reply: "Reply" | ||||
|       repost: "Repost" | ||||
|       renote: "Renote" | ||||
|       add-reaction: "Add your reaction" | ||||
|       detail: "Show detail" | ||||
| 
 | ||||
|  | @ -448,7 +448,7 @@ desktop: | |||
| 
 | ||||
|     mk-post-form-home-widget: | ||||
|       title: "Post" | ||||
|       post: "Post" | ||||
|       note: "Post" | ||||
|       placeholder: "What's happening?" | ||||
| 
 | ||||
|     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: "Reposted!" | ||||
|       failure: "Failed to Repost" | ||||
|       failure: "Failed to Renote" | ||||
| 
 | ||||
|     mk-repost-form-window: | ||||
|       title: "Are you sure you want to repost this post?" | ||||
|     mk-renote-form-window: | ||||
|       title: "Are you sure you want to renote this note?" | ||||
| 
 | ||||
|     mk-user: | ||||
|       last-used-at: "Last used at" | ||||
|  | @ -541,10 +541,10 @@ mobile: | |||
|       notifications: "Notifications" | ||||
|       read-all: "Are you sure you want to mark all unread notifications as read?" | ||||
| 
 | ||||
|     mk-post-page: | ||||
|     mk-note-page: | ||||
|       title: "Post" | ||||
|       prev: "Previous post" | ||||
|       next: "Next post" | ||||
|       prev: "Previous note" | ||||
|       next: "Next note" | ||||
| 
 | ||||
|     mk-search-page: | ||||
|       search: "Search" | ||||
|  | @ -606,33 +606,33 @@ mobile: | |||
|       unfollow: "Unfollow" | ||||
| 
 | ||||
|     mk-home-timeline: | ||||
|       empty-timeline: "There is no posts" | ||||
|       empty-timeline: "There is no notes" | ||||
| 
 | ||||
|     mk-notifications: | ||||
|       more: "More" | ||||
|       empty: "No notifications" | ||||
| 
 | ||||
|     mk-post-detail: | ||||
|     mk-note-detail: | ||||
|       reply: "Reply" | ||||
|       reaction: "Reaction" | ||||
| 
 | ||||
|     mk-post-form: | ||||
|       submit: "Post" | ||||
|       reply-placeholder: "Reply to this post..." | ||||
|       post-placeholder: "What's happening?" | ||||
|       reply-placeholder: "Reply to this note..." | ||||
|       note-placeholder: "What's happening?" | ||||
| 
 | ||||
|     mk-search-posts: | ||||
|       empty: "There is no post related to the 「{}」" | ||||
|     mk-search-notes: | ||||
|       empty: "There is no note related to the 「{}」" | ||||
| 
 | ||||
|     mk-sub-post-content: | ||||
|     mk-sub-note-content: | ||||
|       media-count: "{} media" | ||||
|       poll: "Poll" | ||||
| 
 | ||||
|     mk-timeline-post: | ||||
|     mk-timeline-note: | ||||
|       reposted-by: "Reposted by {}" | ||||
| 
 | ||||
|     mk-timeline: | ||||
|       empty: "No posts" | ||||
|       empty: "No notes" | ||||
|       load-more: "More" | ||||
| 
 | ||||
|     mk-ui-nav: | ||||
|  | @ -652,21 +652,21 @@ mobile: | |||
|       no-users: "No following." | ||||
| 
 | ||||
|     mk-user-timeline: | ||||
|       no-posts: "This user seems never post" | ||||
|       no-posts-with-media: "There is no posts with media" | ||||
|       no-notes: "This user seems never note" | ||||
|       no-notes-with-media: "There is no notes with media" | ||||
|       load-more: "More" | ||||
| 
 | ||||
|     mk-user: | ||||
|       follows-you: "Follows you" | ||||
|       following: "Following" | ||||
|       followers: "Followers" | ||||
|       posts: "Posts" | ||||
|       notes: "Posts" | ||||
|       overview: "Overview" | ||||
|       timeline: "Timeline" | ||||
|       media: "Media" | ||||
| 
 | ||||
|     mk-user-overview: | ||||
|       recent-posts: "Recent posts" | ||||
|       recent-notes: "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 posts" | ||||
|       no-notes: "No notes" | ||||
| 
 | ||||
|     mk-user-overview-photos: | ||||
|       loading: "Loading" | ||||
|  | @ -703,7 +703,7 @@ mobile: | |||
|       load-more: "More" | ||||
| 
 | ||||
| stats: | ||||
|   posts-count: "Number of all posts" | ||||
|   notes-count: "Number of all notes" | ||||
|   users-count: "Number of all users" | ||||
| 
 | ||||
| status: | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ common: | |||
|     confused: "こまこまのこまり" | ||||
|     pudding: "Pudding" | ||||
| 
 | ||||
|   post_categories: | ||||
|   note_categories: | ||||
|     music: "音楽" | ||||
|     game: "ゲーム" | ||||
|     anime: "アニメ" | ||||
|  | @ -124,7 +124,7 @@ common: | |||
|       show-result: "結果を見る" | ||||
|       voted: "投票済み" | ||||
| 
 | ||||
|     mk-post-menu: | ||||
|     mk-note-menu: | ||||
|       pin: "ピン留め" | ||||
|       pinned: "ピン留めしました" | ||||
|       select: "カテゴリを選択" | ||||
|  | @ -211,7 +211,7 @@ ch: | |||
|       textarea: "書いて" | ||||
|       upload: "アップロード" | ||||
|       drive: "ドライブ" | ||||
|       post: "やる" | ||||
|       note: "やる" | ||||
|       posting: "やってます" | ||||
| 
 | ||||
| desktop: | ||||
|  | @ -304,8 +304,8 @@ desktop: | |||
|       settings: "設定" | ||||
|       signout: "サインアウト" | ||||
| 
 | ||||
|     mk-ui-header-post-button: | ||||
|       post: "新規投稿" | ||||
|     mk-ui-header-note-button: | ||||
|       note: "新規投稿" | ||||
| 
 | ||||
|     mk-ui-header-notifications: | ||||
|       title: "通知" | ||||
|  | @ -350,18 +350,18 @@ desktop: | |||
|       no-users: "ミュートしているユーザーはいません" | ||||
| 
 | ||||
|     mk-post-form: | ||||
|       post-placeholder: "いまどうしてる?" | ||||
|       note-placeholder: "いまどうしてる?" | ||||
|       reply-placeholder: "この投稿への返信..." | ||||
|       quote-placeholder: "この投稿を引用..." | ||||
|       post: "投稿" | ||||
|       note: "投稿" | ||||
|       reply: "返信" | ||||
|       repost: "Repost" | ||||
|       renote: "Renote" | ||||
|       posted: "投稿しました!" | ||||
|       replied: "返信しました!" | ||||
|       reposted: "Repostしました!" | ||||
|       post-failed: "投稿に失敗しました" | ||||
|       reposted: "Renoteしました!" | ||||
|       note-failed: "投稿に失敗しました" | ||||
|       reply-failed: "返信に失敗しました" | ||||
|       repost-failed: "Repostに失敗しました" | ||||
|       renote-failed: "Renoteに失敗しました" | ||||
|       posting: "投稿中" | ||||
|       attach-media-from-local: "PCからメディアを添付" | ||||
|       attach-media-from-drive: "ドライブからメディアを添付" | ||||
|  | @ -371,12 +371,12 @@ desktop: | |||
|       text-remain: "のこり{}文字" | ||||
| 
 | ||||
|     mk-post-form-window: | ||||
|       post: "新規投稿" | ||||
|       note: "新規投稿" | ||||
|       reply: "返信" | ||||
|       attaches: "添付: {}メディア" | ||||
|       uploading-media: "{}個のメディアをアップロード中" | ||||
| 
 | ||||
|     mk-post-page: | ||||
|     mk-note-page: | ||||
|       prev: "前の投稿" | ||||
|       next: "次の投稿" | ||||
| 
 | ||||
|  | @ -390,10 +390,10 @@ desktop: | |||
|       other: "その他" | ||||
|       license: "ライセンス" | ||||
| 
 | ||||
|     mk-timeline-post: | ||||
|       reposted-by: "{}がRepost" | ||||
|     mk-timeline-note: | ||||
|       reposted-by: "{}がRenote" | ||||
|       reply: "返信" | ||||
|       repost: "Repost" | ||||
|       renote: "Renote" | ||||
|       add-reaction: "リアクション" | ||||
|       detail: "詳細" | ||||
| 
 | ||||
|  | @ -448,7 +448,7 @@ desktop: | |||
| 
 | ||||
|     mk-post-form-home-widget: | ||||
|       title: "投稿" | ||||
|       post: "投稿" | ||||
|       note: "投稿" | ||||
|       placeholder: "いまどうしてる?" | ||||
| 
 | ||||
|     mk-access-log-home-widget: | ||||
|  | @ -463,16 +463,16 @@ desktop: | |||
|       have-a-nice-day: "良い一日を!" | ||||
|       next: "次" | ||||
| 
 | ||||
|     mk-repost-form: | ||||
|     mk-renote-form: | ||||
|       quote: "引用する..." | ||||
|       cancel: "キャンセル" | ||||
|       repost: "Repost" | ||||
|       renote: "Renote" | ||||
|       reposting: "しています..." | ||||
|       success: "Repostしました!" | ||||
|       failure: "Repostに失敗しました" | ||||
|       success: "Renoteしました!" | ||||
|       failure: "Renoteに失敗しました" | ||||
| 
 | ||||
|     mk-repost-form-window: | ||||
|       title: "この投稿をRepostしますか?" | ||||
|     mk-renote-form-window: | ||||
|       title: "この投稿をRenoteしますか?" | ||||
| 
 | ||||
|     mk-user: | ||||
|       last-used-at: "最終アクセス" | ||||
|  | @ -541,7 +541,7 @@ mobile: | |||
|       notifications: "通知" | ||||
|       read-all: "すべての通知を既読にしますか?" | ||||
| 
 | ||||
|     mk-post-page: | ||||
|     mk-note-page: | ||||
|       title: "投稿" | ||||
|       prev: "前の投稿" | ||||
|       next: "次の投稿" | ||||
|  | @ -612,24 +612,24 @@ mobile: | |||
|       more: "もっと見る" | ||||
|       empty: "ありません!" | ||||
| 
 | ||||
|     mk-post-detail: | ||||
|     mk-note-detail: | ||||
|       reply: "返信" | ||||
|       reaction: "リアクション" | ||||
| 
 | ||||
|     mk-post-form: | ||||
|       submit: "投稿" | ||||
|       reply-placeholder: "この投稿への返信..." | ||||
|       post-placeholder: "いまどうしてる?" | ||||
|       note-placeholder: "いまどうしてる?" | ||||
| 
 | ||||
|     mk-search-posts: | ||||
|     mk-search-notes: | ||||
|       empty: "「{}」に関する投稿は見つかりませんでした。" | ||||
| 
 | ||||
|     mk-sub-post-content: | ||||
|     mk-sub-note-content: | ||||
|       media-count: "{}個のメディア" | ||||
|       poll: "投票" | ||||
| 
 | ||||
|     mk-timeline-post: | ||||
|       reposted-by: "{}がRepost" | ||||
|     mk-timeline-note: | ||||
|       reposted-by: "{}がRenote" | ||||
| 
 | ||||
|     mk-timeline: | ||||
|       empty: "表示するものがありません" | ||||
|  | @ -652,21 +652,21 @@ mobile: | |||
|       no-users: "フォロー中のユーザーはいないようです。" | ||||
| 
 | ||||
|     mk-user-timeline: | ||||
|       no-posts: "このユーザーはまだ投稿していないようです。" | ||||
|       no-posts-with-media: "メディア付き投稿はありません。" | ||||
|       no-notes: "このユーザーはまだ投稿していないようです。" | ||||
|       no-notes-with-media: "メディア付き投稿はありません。" | ||||
|       load-more: "もっとみる" | ||||
| 
 | ||||
|     mk-user: | ||||
|       follows-you: "フォローされています" | ||||
|       following: "フォロー" | ||||
|       followers: "フォロワー" | ||||
|       posts: "投稿" | ||||
|       notes: "投稿" | ||||
|       overview: "概要" | ||||
|       timeline: "タイムライン" | ||||
|       media: "メディア" | ||||
| 
 | ||||
|     mk-user-overview: | ||||
|       recent-posts: "最近の投稿" | ||||
|       recent-notes: "最近の投稿" | ||||
|       images: "画像" | ||||
|       activity: "アクティビティ" | ||||
|       keywords: "キーワード" | ||||
|  | @ -675,9 +675,9 @@ mobile: | |||
|       followers-you-know: "知り合いのフォロワー" | ||||
|       last-used-at: "最終ログイン" | ||||
| 
 | ||||
|     mk-user-overview-posts: | ||||
|     mk-user-overview-notes: | ||||
|       loading: "読み込み中" | ||||
|       no-posts: "投稿はありません" | ||||
|       no-notes: "投稿はありません" | ||||
| 
 | ||||
|     mk-user-overview-photos: | ||||
|       loading: "読み込み中" | ||||
|  | @ -703,7 +703,7 @@ mobile: | |||
|       load-more: "もっと" | ||||
| 
 | ||||
| stats: | ||||
|   posts-count: "投稿の数" | ||||
|   notes-count: "投稿の数" | ||||
|   users-count: "アカウントの数" | ||||
| 
 | ||||
| status: | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 				<template v-for="p in app.permission"> | ||||
| 					<li v-if="p == 'account-read'">アカウントの情報を見る。</li> | ||||
| 					<li v-if="p == 'account-write'">アカウントの情報を操作する。</li> | ||||
| 					<li v-if="p == 'post-write'">投稿する。</li> | ||||
| 					<li v-if="p == 'note-write'">投稿する。</li> | ||||
| 					<li v-if="p == 'like-write'">いいねしたりいいね解除する。</li> | ||||
| 					<li v-if="p == 'following-write'">フォローしたりフォロー解除する。</li> | ||||
| 					<li v-if="p == 'drive-read'">ドライブを見る。</li> | ||||
|  |  | |||
|  | @ -15,11 +15,11 @@ | |||
| 		</div> | ||||
| 
 | ||||
| 		<div class="body"> | ||||
| 			<p v-if="postsFetching">読み込み中<mk-ellipsis/></p> | ||||
| 			<div v-if="!postsFetching"> | ||||
| 				<p v-if="posts == null || posts.length == 0">まだ投稿がありません</p> | ||||
| 				<template v-if="posts != null"> | ||||
| 					<mk-channel-post each={ post in posts.slice().reverse() } post={ post } form={ parent.refs.form }/> | ||||
| 			<p v-if="notesFetching">読み込み中<mk-ellipsis/></p> | ||||
| 			<div v-if="!notesFetching"> | ||||
| 				<p v-if="notes == null || notes.length == 0">まだ投稿がありません</p> | ||||
| 				<template v-if="notes != null"> | ||||
| 					<mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/> | ||||
| 				</template> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | @ -62,9 +62,9 @@ | |||
| 
 | ||||
| 		this.id = this.opts.id; | ||||
| 		this.fetching = true; | ||||
| 		this.postsFetching = true; | ||||
| 		this.notesFetching = true; | ||||
| 		this.channel = null; | ||||
| 		this.posts = null; | ||||
| 		this.notes = null; | ||||
| 		this.connection = new ChannelStream(this.id); | ||||
| 		this.unreadCount = 0; | ||||
| 
 | ||||
|  | @ -95,9 +95,9 @@ | |||
| 			}); | ||||
| 
 | ||||
| 			// 投稿読み込み | ||||
| 			this.$root.$data.os.api('channels/posts', { | ||||
| 			this.$root.$data.os.api('channels/notes', { | ||||
| 				channelId: this.id | ||||
| 			}).then(posts => { | ||||
| 			}).then(notes => { | ||||
| 				if (fetched) { | ||||
| 					Progress.done(); | ||||
| 				} else { | ||||
|  | @ -106,26 +106,26 @@ | |||
| 				} | ||||
| 
 | ||||
| 				this.update({ | ||||
| 					postsFetching: false, | ||||
| 					posts: posts | ||||
| 					notesFetching: false, | ||||
| 					notes: notes | ||||
| 				}); | ||||
| 			}); | ||||
| 
 | ||||
| 			this.connection.on('post', this.onPost); | ||||
| 			this.connection.on('note', this.onNote); | ||||
| 			document.addEventListener('visibilitychange', this.onVisibilitychange, false); | ||||
| 		}); | ||||
| 
 | ||||
| 		this.on('unmount', () => { | ||||
| 			this.connection.off('post', this.onPost); | ||||
| 			this.connection.off('note', this.onNote); | ||||
| 			this.connection.close(); | ||||
| 			document.removeEventListener('visibilitychange', this.onVisibilitychange); | ||||
| 		}); | ||||
| 
 | ||||
| 		this.onPost = post => { | ||||
| 			this.posts.unshift(post); | ||||
| 		this.onNote = note => { | ||||
| 			this.notes.unshift(note); | ||||
| 			this.update(); | ||||
| 
 | ||||
| 			if (document.hidden && this.$root.$data.os.isSignedIn && post.userId !== this.$root.$data.os.i.id) { | ||||
| 			if (document.hidden && this.$root.$data.os.isSignedIn && note.userId !== this.$root.$data.os.i.id) { | ||||
| 				this.unreadCount++; | ||||
| 				document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`; | ||||
| 			} | ||||
|  | @ -162,19 +162,19 @@ | |||
| 	</script> | ||||
| </mk-channel> | ||||
| 
 | ||||
| <mk-channel-post> | ||||
| <mk-channel-note> | ||||
| 	<header> | ||||
| 		<a class="index" @click="reply">{ post.index }:</a> | ||||
| 		<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(post.user) }</b></a> | ||||
| 		<mk-time time={ post.createdAt }/> | ||||
| 		<mk-time time={ post.createdAt } mode="detail"/> | ||||
| 		<a class="index" @click="reply">{ note.index }:</a> | ||||
| 		<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a> | ||||
| 		<mk-time time={ note.createdAt }/> | ||||
| 		<mk-time time={ note.createdAt } mode="detail"/> | ||||
| 		<span>ID:<i>{ acct }</i></span> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<a v-if="post.reply">>>{ post.reply.index }</a> | ||||
| 		{ post.text } | ||||
| 		<div class="media" v-if="post.media"> | ||||
| 			<template each={ file in post.media }> | ||||
| 		<a v-if="note.reply">>>{ note.reply.index }</a> | ||||
| 		{ note.text } | ||||
| 		<div class="media" v-if="note.media"> | ||||
| 			<template each={ file in note.media }> | ||||
| 				<a href={ file.url } target="_blank"> | ||||
| 					<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/> | ||||
| 				</a> | ||||
|  | @ -232,18 +232,18 @@ | |||
| 		import getAcct from '../../../../acct/render'; | ||||
| 		import getUserName from '../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| 		this.post = this.opts.post; | ||||
| 		this.note = this.opts.note; | ||||
| 		this.form = this.opts.form; | ||||
| 		this.acct = getAcct(this.post.user); | ||||
| 		this.name = getUserName(this.post.user); | ||||
| 		this.acct = getAcct(this.note.user); | ||||
| 		this.name = getUserName(this.note.user); | ||||
| 
 | ||||
| 		this.reply = () => { | ||||
| 			this.form.update({ | ||||
| 				reply: this.post | ||||
| 				reply: this.note | ||||
| 			}); | ||||
| 		}; | ||||
| 	</script> | ||||
| </mk-channel-post> | ||||
| </mk-channel-note> | ||||
| 
 | ||||
| <mk-channel-form> | ||||
| 	<p v-if="reply"><b>>>{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p> | ||||
|  | @ -251,8 +251,8 @@ | |||
| 	<div class="actions"> | ||||
| 		<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button> | ||||
| 		<button @click="drive">%fa:cloud%%i18n:ch.tags.mk-channel-form.drive%</button> | ||||
| 		<button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="post"> | ||||
| 			<template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:ch.tags.mk-channel-form.posting%' : '%i18n:ch.tags.mk-channel-form.post%' }<mk-ellipsis v-if="wait"/> | ||||
| 		<button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="note"> | ||||
| 			<template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:ch.tags.mk-channel-form.posting%' : '%i18n:ch.tags.mk-channel-form.note%' }<mk-ellipsis v-if="wait"/> | ||||
| 		</button> | ||||
| 	</div> | ||||
| 	<mk-uploader ref="uploader"/> | ||||
|  | @ -321,7 +321,7 @@ | |||
| 			this.$refs.text.value = ''; | ||||
| 		}; | ||||
| 
 | ||||
| 		this.post = () => { | ||||
| 		this.note = () => { | ||||
| 			this.update({ | ||||
| 				wait: true | ||||
| 			}); | ||||
|  | @ -330,7 +330,7 @@ | |||
| 				? this.files.map(f => f.id) | ||||
| 				: undefined; | ||||
| 
 | ||||
| 			this.$root.$data.os.api('posts/create', { | ||||
| 			this.$root.$data.os.api('notes/create', { | ||||
| 				text: this.$refs.text.value == '' ? undefined : this.$refs.text.value, | ||||
| 				mediaIds: files, | ||||
| 				replyId: this.reply ? this.reply.id : undefined, | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ export type API = { | |||
| 
 | ||||
| 	post: (opts?: { | ||||
| 		reply?: any; | ||||
| 		repost?: any; | ||||
| 		renote?: any; | ||||
| 	}) => void; | ||||
| 
 | ||||
| 	notify: (message: string) => void; | ||||
|  | @ -312,7 +312,7 @@ export default class MiOS extends EventEmitter { | |||
| 			// Finish init
 | ||||
| 			callback(); | ||||
| 
 | ||||
| 			//#region Post
 | ||||
| 			//#region Note
 | ||||
| 
 | ||||
| 			// Init service worker
 | ||||
| 			if (this.shouldRegisterSw) this.registerSw(); | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import getPostSummary from '../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../renderers/get-note-summary'; | ||||
| import getReactionEmoji from '../../../../renderers/get-reaction-emoji'; | ||||
| import getUserName from '../../../../renderers/get-user-name'; | ||||
| 
 | ||||
|  | @ -23,28 +23,28 @@ export default function(type, data): Notification { | |||
| 		case 'mention': | ||||
| 			return { | ||||
| 				title: `${getUserName(data.user)}さんから:`, | ||||
| 				body: getPostSummary(data), | ||||
| 				body: getNoteSummary(data), | ||||
| 				icon: data.user.avatarUrl + '?thumbnail&size=64' | ||||
| 			}; | ||||
| 
 | ||||
| 		case 'reply': | ||||
| 			return { | ||||
| 				title: `${getUserName(data.user)}さんから返信:`, | ||||
| 				body: getPostSummary(data), | ||||
| 				body: getNoteSummary(data), | ||||
| 				icon: data.user.avatarUrl + '?thumbnail&size=64' | ||||
| 			}; | ||||
| 
 | ||||
| 		case 'quote': | ||||
| 			return { | ||||
| 				title: `${getUserName(data.user)}さんが引用:`, | ||||
| 				body: getPostSummary(data), | ||||
| 				body: getNoteSummary(data), | ||||
| 				icon: data.user.avatarUrl + '?thumbnail&size=64' | ||||
| 			}; | ||||
| 
 | ||||
| 		case 'reaction': | ||||
| 			return { | ||||
| 				title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, | ||||
| 				body: getPostSummary(data.post), | ||||
| 				body: getNoteSummary(data.note), | ||||
| 				icon: data.user.avatarUrl + '?thumbnail&size=64' | ||||
| 			}; | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,8 +19,8 @@ export default function(qs: string) { | |||
| 				case 'reply': | ||||
| 					q['reply'] = value == 'null' ? null : value == 'true'; | ||||
| 					break; | ||||
| 				case 'repost': | ||||
| 					q['repost'] = value == 'null' ? null : value == 'true'; | ||||
| 				case 'renote': | ||||
| 					q['renote'] = value == 'null' ? null : value == 'true'; | ||||
| 					break; | ||||
| 				case 'media': | ||||
| 					q['media'] = value == 'null' ? null : value == 'true'; | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import signin from './signin.vue'; | |||
| import signup from './signup.vue'; | ||||
| import forkit from './forkit.vue'; | ||||
| import nav from './nav.vue'; | ||||
| import postHtml from './post-html'; | ||||
| import noteHtml from './note-html'; | ||||
| import poll from './poll.vue'; | ||||
| import pollEditor from './poll-editor.vue'; | ||||
| import reactionIcon from './reaction-icon.vue'; | ||||
|  | @ -29,7 +29,7 @@ Vue.component('mk-signin', signin); | |||
| Vue.component('mk-signup', signup); | ||||
| Vue.component('mk-forkit', forkit); | ||||
| Vue.component('mk-nav', nav); | ||||
| Vue.component('mk-post-html', postHtml); | ||||
| Vue.component('mk-note-html', noteHtml); | ||||
| Vue.component('mk-poll', poll); | ||||
| Vue.component('mk-poll-editor', pollEditor); | ||||
| Vue.component('mk-reaction-icon', reactionIcon); | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| 				<img src="/assets/desktop/messaging/delete.png" alt="Delete"/> | ||||
| 			</button> | ||||
| 			<div class="content" v-if="!message.isDeleted"> | ||||
| 				<mk-post-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/> | ||||
| 				<mk-note-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/> | ||||
| 				<div class="file" v-if="message.file"> | ||||
| 					<a :href="message.file.url" target="_blank" :title="message.file.name"> | ||||
| 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ const flatten = list => list.reduce( | |||
| 	(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] | ||||
| ); | ||||
| 
 | ||||
| export default Vue.component('mk-post-html', { | ||||
| export default Vue.component('mk-note-html', { | ||||
| 	props: { | ||||
| 		text: { | ||||
| 			type: String, | ||||
|  | @ -1,8 +1,8 @@ | |||
| <template> | ||||
| <div class="mk-post-menu"> | ||||
| <div class="mk-note-menu"> | ||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||
| 	<div class="popover" :class="{ compact }" ref="popover"> | ||||
| 		<button v-if="post.userId == os.i.id" @click="pin">%i18n:common.tags.mk-post-menu.pin%</button> | ||||
| 		<button v-if="note.userId == os.i.id" @click="pin">%i18n:common.tags.mk-note-menu.pin%</button> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -12,7 +12,7 @@ import Vue from 'vue'; | |||
| import * as anime from 'animejs'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post', 'source', 'compact'], | ||||
| 	props: ['note', 'source', 'compact'], | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			const popover = this.$refs.popover as any; | ||||
|  | @ -51,7 +51,7 @@ export default Vue.extend({ | |||
| 	methods: { | ||||
| 		pin() { | ||||
| 			(this as any).api('i/pin', { | ||||
| 				postId: this.post.id | ||||
| 				noteId: this.note.id | ||||
| 			}).then(() => { | ||||
| 				this.$destroy(); | ||||
| 			}); | ||||
|  | @ -83,7 +83,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| $border-color = rgba(27, 31, 35, 0.15) | ||||
| 
 | ||||
| .mk-post-menu | ||||
| .mk-note-menu | ||||
| 	position initial | ||||
| 
 | ||||
| 	> .backdrop | ||||
|  | @ -22,7 +22,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			showResult: false | ||||
|  | @ -30,7 +30,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	computed: { | ||||
| 		poll(): any { | ||||
| 			return this.post.poll; | ||||
| 			return this.note.poll; | ||||
| 		}, | ||||
| 		total(): number { | ||||
| 			return this.poll.choices.reduce((a, b) => a + b.votes, 0); | ||||
|  | @ -48,8 +48,8 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		vote(id) { | ||||
| 			if (this.poll.choices.some(c => c.isVoted)) return; | ||||
| 			(this as any).api('posts/polls/vote', { | ||||
| 				postId: this.post.id, | ||||
| 			(this as any).api('notes/polls/vote', { | ||||
| 				noteId: this.note.id, | ||||
| 				choice: id | ||||
| 			}).then(() => { | ||||
| 				this.poll.choices.forEach(c => { | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ import * as anime from 'animejs'; | |||
| const placeholder = '%i18n:common.tags.mk-reaction-picker.choose-reaction%'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post', 'source', 'compact', 'cb'], | ||||
| 	props: ['note', 'source', 'compact', 'cb'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			title: placeholder | ||||
|  | @ -68,8 +68,8 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	methods: { | ||||
| 		react(reaction) { | ||||
| 			(this as any).api('posts/reactions/create', { | ||||
| 				postId: this.post.id, | ||||
| 			(this as any).api('notes/reactions/create', { | ||||
| 				noteId: this.note.id, | ||||
| 				reaction: reaction | ||||
| 			}).then(() => { | ||||
| 				if (this.cb) this.cb(); | ||||
|  |  | |||
|  | @ -17,10 +17,10 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		reactions(): number { | ||||
| 			return this.post.reactionCounts; | ||||
| 			return this.note.reactionCounts; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -1,21 +1,21 @@ | |||
| <template> | ||||
| <div class="mk-welcome-timeline"> | ||||
| 	<div v-for="post in posts"> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id"> | ||||
| 			<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 	<div v-for="note in notes"> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id"> | ||||
| 			<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="body"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ getUserName(post.user) }}</router-link> | ||||
| 				<span class="username">@{{ getAcct(post.user) }}</span> | ||||
| 				<router-link class="name" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id">{{ getUserName(note.user) }}</router-link> | ||||
| 				<span class="username">@{{ getAcct(note.user) }}</span> | ||||
| 				<div class="info"> | ||||
| 					<router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`"> | ||||
| 						<mk-time :time="post.createdAt"/> | ||||
| 					<router-link class="created-at" :to="`/@${getAcct(note.user)}/${note.id}`"> | ||||
| 						<mk-time :time="note.createdAt"/> | ||||
| 					</router-link> | ||||
| 				</div> | ||||
| 			</header> | ||||
| 			<div class="text"> | ||||
| 				<mk-post-html :text="post.text"/> | ||||
| 				<mk-note-html :text="note.text"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | @ -31,7 +31,7 @@ export default Vue.extend({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			posts: [] | ||||
| 			notes: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
|  | @ -42,14 +42,14 @@ export default Vue.extend({ | |||
| 		getUserName, | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 			(this as any).api('posts', { | ||||
| 			(this as any).api('notes', { | ||||
| 				reply: false, | ||||
| 				repost: false, | ||||
| 				renote: false, | ||||
| 				media: false, | ||||
| 				poll: false, | ||||
| 				bot: false | ||||
| 			}).then(posts => { | ||||
| 				this.posts = posts; | ||||
| 			}).then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| import PostFormWindow from '../views/components/post-form-window.vue'; | ||||
| import RepostFormWindow from '../views/components/repost-form-window.vue'; | ||||
| import RenoteFormWindow from '../views/components/renote-form-window.vue'; | ||||
| 
 | ||||
| export default function(opts) { | ||||
| 	const o = opts || {}; | ||||
| 	if (o.repost) { | ||||
| 		const vm = new RepostFormWindow({ | ||||
| 	if (o.renote) { | ||||
| 		const vm = new RenoteFormWindow({ | ||||
| 			propsData: { | ||||
| 				repost: o.repost | ||||
| 				renote: o.renote | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 		document.body.appendChild(vm.$el); | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ import MkSelectDrive from './views/pages/selectdrive.vue'; | |||
| import MkDrive from './views/pages/drive.vue'; | ||||
| import MkHomeCustomize from './views/pages/home-customize.vue'; | ||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||
| import MkPost from './views/pages/post.vue'; | ||||
| import MkNote from './views/pages/note.vue'; | ||||
| import MkSearch from './views/pages/search.vue'; | ||||
| import MkOthello from './views/pages/othello.vue'; | ||||
| 
 | ||||
|  | @ -57,7 +57,7 @@ init(async (launch) => { | |||
| 			{ path: '/othello', component: MkOthello }, | ||||
| 			{ path: '/othello/:game', component: MkOthello }, | ||||
| 			{ path: '/@:user', component: MkUser }, | ||||
| 			{ path: '/@:user/:post', component: MkPost } | ||||
| 			{ path: '/@:user/:note', component: MkNote } | ||||
| 		] | ||||
| 	}); | ||||
| 
 | ||||
|  | @ -114,8 +114,8 @@ function registerNotifications(stream: HomeStreamManager) { | |||
| 			setTimeout(n.close.bind(n), 5000); | ||||
| 		}); | ||||
| 
 | ||||
| 		connection.on('mention', post => { | ||||
| 			const _n = composeNotification('mention', post); | ||||
| 		connection.on('mention', note => { | ||||
| 			const _n = composeNotification('mention', note); | ||||
| 			const n = new Notification(_n.title, { | ||||
| 				body: _n.body, | ||||
| 				icon: _n.icon | ||||
|  | @ -123,8 +123,8 @@ function registerNotifications(stream: HomeStreamManager) { | |||
| 			setTimeout(n.close.bind(n), 6000); | ||||
| 		}); | ||||
| 
 | ||||
| 		connection.on('reply', post => { | ||||
| 			const _n = composeNotification('reply', post); | ||||
| 		connection.on('reply', note => { | ||||
| 			const _n = composeNotification('reply', note); | ||||
| 			const n = new Notification(_n.title, { | ||||
| 				body: _n.body, | ||||
| 				icon: _n.icon | ||||
|  | @ -132,8 +132,8 @@ function registerNotifications(stream: HomeStreamManager) { | |||
| 			setTimeout(n.close.bind(n), 6000); | ||||
| 		}); | ||||
| 
 | ||||
| 		connection.on('quote', post => { | ||||
| 			const _n = composeNotification('quote', post); | ||||
| 		connection.on('quote', note => { | ||||
| 			const _n = composeNotification('quote', note); | ||||
| 			const n = new Notification(_n.title, { | ||||
| 				body: _n.body, | ||||
| 				icon: _n.icon | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ import Vue from 'vue'; | |||
| export default Vue.extend({ | ||||
| 	props: ['data'], | ||||
| 	created() { | ||||
| 		this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); | ||||
| 		this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); | ||||
| 		const peak = Math.max.apply(null, this.data.map(d => d.total)); | ||||
| 
 | ||||
| 		let x = 0; | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| <template> | ||||
| <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown"> | ||||
| 	<title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title> | ||||
| 	<title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title> | ||||
| 	<polyline | ||||
| 		:points="pointsPost" | ||||
| 		:points="pointsNote" | ||||
| 		fill="none" | ||||
| 		stroke-width="1" | ||||
| 		stroke="#41ddde"/> | ||||
|  | @ -12,7 +12,7 @@ | |||
| 		stroke-width="1" | ||||
| 		stroke="#f7796c"/> | ||||
| 	<polyline | ||||
| 		:points="pointsRepost" | ||||
| 		:points="pointsRenote" | ||||
| 		fill="none" | ||||
| 		stroke-width="1" | ||||
| 		stroke="#a1de41"/> | ||||
|  | @ -48,24 +48,24 @@ export default Vue.extend({ | |||
| 			viewBoxY: 60, | ||||
| 			zoom: 1, | ||||
| 			pos: 0, | ||||
| 			pointsPost: null, | ||||
| 			pointsNote: null, | ||||
| 			pointsReply: null, | ||||
| 			pointsRepost: null, | ||||
| 			pointsRenote: null, | ||||
| 			pointsTotal: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		this.data.reverse(); | ||||
| 		this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); | ||||
| 		this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			const peak = Math.max.apply(null, this.data.map(d => d.total)); | ||||
| 			if (peak != 0) { | ||||
| 				this.pointsPost = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsNote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsReply = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsRepost = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsRenote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsTotal = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); | ||||
| 			} | ||||
| 		}, | ||||
|  |  | |||
|  | @ -4,23 +4,23 @@ import ui from './ui.vue'; | |||
| import uiNotification from './ui-notification.vue'; | ||||
| import home from './home.vue'; | ||||
| import timeline from './timeline.vue'; | ||||
| import posts from './posts.vue'; | ||||
| import subPostContent from './sub-post-content.vue'; | ||||
| import notes from './notes.vue'; | ||||
| import subNoteContent from './sub-note-content.vue'; | ||||
| import window from './window.vue'; | ||||
| import postFormWindow from './post-form-window.vue'; | ||||
| import repostFormWindow from './repost-form-window.vue'; | ||||
| import noteFormWindow from './post-form-window.vue'; | ||||
| import renoteFormWindow from './renote-form-window.vue'; | ||||
| import analogClock from './analog-clock.vue'; | ||||
| import ellipsisIcon from './ellipsis-icon.vue'; | ||||
| import mediaImage from './media-image.vue'; | ||||
| import mediaImageDialog from './media-image-dialog.vue'; | ||||
| import mediaVideo from './media-video.vue'; | ||||
| import notifications from './notifications.vue'; | ||||
| import postForm from './post-form.vue'; | ||||
| import repostForm from './repost-form.vue'; | ||||
| import noteForm from './post-form.vue'; | ||||
| import renoteForm from './renote-form.vue'; | ||||
| import followButton from './follow-button.vue'; | ||||
| import postPreview from './post-preview.vue'; | ||||
| import notePreview from './note-preview.vue'; | ||||
| import drive from './drive.vue'; | ||||
| import postDetail from './post-detail.vue'; | ||||
| import noteDetail from './note-detail.vue'; | ||||
| import settings from './settings.vue'; | ||||
| import calendar from './calendar.vue'; | ||||
| import activity from './activity.vue'; | ||||
|  | @ -34,23 +34,23 @@ Vue.component('mk-ui', ui); | |||
| Vue.component('mk-ui-notification', uiNotification); | ||||
| Vue.component('mk-home', home); | ||||
| Vue.component('mk-timeline', timeline); | ||||
| Vue.component('mk-posts', posts); | ||||
| Vue.component('mk-sub-post-content', subPostContent); | ||||
| Vue.component('mk-notes', notes); | ||||
| Vue.component('mk-sub-note-content', subNoteContent); | ||||
| Vue.component('mk-window', window); | ||||
| Vue.component('mk-post-form-window', postFormWindow); | ||||
| Vue.component('mk-repost-form-window', repostFormWindow); | ||||
| Vue.component('mk-post-form-window', noteFormWindow); | ||||
| Vue.component('mk-renote-form-window', renoteFormWindow); | ||||
| Vue.component('mk-analog-clock', analogClock); | ||||
| Vue.component('mk-ellipsis-icon', ellipsisIcon); | ||||
| Vue.component('mk-media-image', mediaImage); | ||||
| Vue.component('mk-media-image-dialog', mediaImageDialog); | ||||
| Vue.component('mk-media-video', mediaVideo); | ||||
| Vue.component('mk-notifications', notifications); | ||||
| Vue.component('mk-post-form', postForm); | ||||
| Vue.component('mk-repost-form', repostForm); | ||||
| Vue.component('mk-post-form', noteForm); | ||||
| Vue.component('mk-renote-form', renoteForm); | ||||
| Vue.component('mk-follow-button', followButton); | ||||
| Vue.component('mk-post-preview', postPreview); | ||||
| Vue.component('mk-note-preview', notePreview); | ||||
| Vue.component('mk-drive', drive); | ||||
| Vue.component('mk-post-detail', postDetail); | ||||
| Vue.component('mk-note-detail', noteDetail); | ||||
| Vue.component('mk-settings', settings); | ||||
| Vue.component('mk-calendar', calendar); | ||||
| Vue.component('mk-activity', activity); | ||||
|  |  | |||
|  | @ -7,12 +7,12 @@ | |||
| 	<div class="fetching" v-if="fetching"> | ||||
| 		<mk-ellipsis-icon/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="posts.length == 0 && !fetching"> | ||||
| 	<p class="empty" v-if="notes.length == 0 && !fetching"> | ||||
| 		%fa:R comments% | ||||
| 		<span v-if="mode == 'all'">あなた宛ての投稿はありません。</span> | ||||
| 		<span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません。</span> | ||||
| 	</p> | ||||
| 	<mk-posts :posts="posts" ref="timeline"/> | ||||
| 	<mk-notes :notes="notes" ref="timeline"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -24,7 +24,7 @@ export default Vue.extend({ | |||
| 			fetching: true, | ||||
| 			moreFetching: false, | ||||
| 			mode: 'all', | ||||
| 			posts: [] | ||||
| 			notes: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
|  | @ -56,23 +56,23 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 			this.posts =  []; | ||||
| 			(this as any).api('posts/mentions', { | ||||
| 			this.notes =  []; | ||||
| 			(this as any).api('notes/mentions', { | ||||
| 				following: this.mode == 'following' | ||||
| 			}).then(posts => { | ||||
| 				this.posts = posts; | ||||
| 			}).then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				if (cb) cb(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			if (this.moreFetching || this.fetching || this.posts.length == 0) return; | ||||
| 			if (this.moreFetching || this.fetching || this.notes.length == 0) return; | ||||
| 			this.moreFetching = true; | ||||
| 			(this as any).api('posts/mentions', { | ||||
| 			(this as any).api('notes/mentions', { | ||||
| 				following: this.mode == 'following', | ||||
| 				untilId: this.posts[this.posts.length - 1].id | ||||
| 			}).then(posts => { | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				untilId: this.notes[this.notes.length - 1].id | ||||
| 			}).then(notes => { | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,24 +1,24 @@ | |||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<div class="left"> | ||||
| 				<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</router-link> | ||||
| 				<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</router-link> | ||||
| 				<span class="username">@{{ acct }}</span> | ||||
| 			</div> | ||||
| 			<div class="right"> | ||||
| 				<router-link class="time" :to="`/@${acct}/${post.id}`"> | ||||
| 					<mk-time :time="post.createdAt"/> | ||||
| 				<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 					<mk-time :time="note.createdAt"/> | ||||
| 				</router-link> | ||||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-post-html v-if="post.text" :text="post.text" :i="os.i" :class="$style.text"/> | ||||
| 			<div class="media" v-if="post.media > 0"> | ||||
| 				<mk-media-list :media-list="post.media"/> | ||||
| 			<mk-note-html v-if="note.text" :text="note.text" :i="os.i" :class="$style.text"/> | ||||
| 			<div class="media" v-if="note.media > 0"> | ||||
| 				<mk-media-list :media-list="note.media"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | @ -32,16 +32,16 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.post.createdAt); | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										448
									
								
								src/client/app/desktop/views/components/note-detail.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										448
									
								
								src/client/app/desktop/views/components/note-detail.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="mk-post-preview" :title="title"> | ||||
| <div class="mk-note-preview" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="time" :to="`/@${acct}/${post.id}`"> | ||||
| 				<mk-time :time="post.createdAt"/> | ||||
| 			<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-post-content class="text" :post="post"/> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -25,23 +25,23 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.post.createdAt); | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-post-preview | ||||
| .mk-note-preview | ||||
| 	font-size 0.9em | ||||
| 	background #fff | ||||
| 
 | ||||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="post.userId"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="created-at" :to="`/@${acct}/${post.id}`"> | ||||
| 				<mk-time :time="post.createdAt"/> | ||||
| 			<router-link class="created-at" :to="`/@${acct}/${note.id}`"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-post-content class="text" :post="post"/> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -25,16 +25,16 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.post.createdAt); | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										596
									
								
								src/client/app/desktop/views/components/notes.note.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										596
									
								
								src/client/app/desktop/views/components/notes.note.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,10 +1,10 @@ | |||
| <template> | ||||
| <div class="mk-posts"> | ||||
| 	<template v-for="(post, i) in _posts"> | ||||
| 		<x-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> | ||||
| 		<p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> | ||||
| 			<span>%fa:angle-up%{{ post._datetext }}</span> | ||||
| 			<span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> | ||||
| <div class="mk-notes"> | ||||
| 	<template v-for="(note, i) in _notes"> | ||||
| 		<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> | ||||
| 		<p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> | ||||
| 			<span>%fa:angle-up%{{ note._datetext }}</span> | ||||
| 			<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> | ||||
| 		</p> | ||||
| 	</template> | ||||
| 	<footer> | ||||
|  | @ -15,26 +15,26 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XPost from './posts.post.vue'; | ||||
| import XNote from './notes.note.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XPost | ||||
| 		XNote | ||||
| 	}, | ||||
| 	props: { | ||||
| 		posts: { | ||||
| 		notes: { | ||||
| 			type: Array, | ||||
| 			default: () => [] | ||||
| 		} | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		_posts(): any[] { | ||||
| 			return (this.posts as any).map(post => { | ||||
| 				const date = new Date(post.createdAt).getDate(); | ||||
| 				const month = new Date(post.createdAt).getMonth() + 1; | ||||
| 				post._date = date; | ||||
| 				post._datetext = `${month}月 ${date}日`; | ||||
| 				return post; | ||||
| 		_notes(): any[] { | ||||
| 			return (this.notes as any).map(note => { | ||||
| 				const date = new Date(note.createdAt).getDate(); | ||||
| 				const month = new Date(note.createdAt).getMonth() + 1; | ||||
| 				note._date = date; | ||||
| 				note._datetext = `${month}月 ${date}日`; | ||||
| 				return note; | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | @ -42,15 +42,15 @@ export default Vue.extend({ | |||
| 		focus() { | ||||
| 			(this.$el as any).children[0].focus(); | ||||
| 		}, | ||||
| 		onPostUpdated(i, post) { | ||||
| 			Vue.set((this as any).posts, i, post); | ||||
| 		onNoteUpdated(i, note) { | ||||
| 			Vue.set((this as any).notes, i, note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-posts | ||||
| .mk-notes | ||||
| 
 | ||||
| 	> .date | ||||
| 		display block | ||||
|  | @ -13,33 +13,33 @@ | |||
| 							<mk-reaction-icon :reaction="notification.reaction"/> | ||||
| 							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> | ||||
| 							%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| 						</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'repost'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> | ||||
| 						<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 				<template v-if="notification.type == 'renote'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:retweet% | ||||
| 							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> | ||||
| 							%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% | ||||
| 						</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'quote'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> | ||||
| 						<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:quote-left% | ||||
| 							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> | ||||
| 						<router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'follow'"> | ||||
|  | @ -53,25 +53,25 @@ | |||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'reply'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> | ||||
| 						<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:reply% | ||||
| 							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> | ||||
| 						<router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'mention'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId"> | ||||
| 						<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:at% | ||||
| 							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 						</p> | ||||
| 						<a class="post-preview" :href="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</a> | ||||
| 						<a class="note-preview" :href="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</a> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'poll_vote'"> | ||||
|  | @ -80,8 +80,8 @@ | |||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p> | ||||
| 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> | ||||
| 							%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| 						</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
|  | @ -103,7 +103,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getPostSummary from '../../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|  | @ -115,7 +115,7 @@ export default Vue.extend({ | |||
| 			moreNotifications: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			getPostSummary | ||||
| 			getNoteSummary | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
|  | @ -241,10 +241,10 @@ export default Vue.extend({ | |||
| 					i, .mk-reaction-icon | ||||
| 						margin-right 4px | ||||
| 
 | ||||
| 			.post-preview | ||||
| 			.note-preview | ||||
| 				color rgba(0, 0, 0, 0.7) | ||||
| 
 | ||||
| 			.post-ref | ||||
| 			.note-ref | ||||
| 				color rgba(0, 0, 0, 0.7) | ||||
| 
 | ||||
| 				[data-fa] | ||||
|  | @ -254,7 +254,7 @@ export default Vue.extend({ | |||
| 					display inline-block | ||||
| 					margin-right 3px | ||||
| 
 | ||||
| 			&.repost, &.quote | ||||
| 			&.renote, &.quote | ||||
| 				.text p i | ||||
| 					color #77B255 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <div class="mk-post-detail" :title="title"> | ||||
| <div class="mk-note-detail" :title="title"> | ||||
| 	<button | ||||
| 		class="read-more" | ||||
| 		v-if="p.reply && p.reply.replyId && context == null" | ||||
|  | @ -11,19 +11,19 @@ | |||
| 		<template v-if="contextFetching">%fa:spinner .pulse%</template> | ||||
| 	</button> | ||||
| 	<div class="context"> | ||||
| 		<x-sub v-for="post in context" :key="post.id" :post="post"/> | ||||
| 		<x-sub v-for="note in context" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :post="p.reply"/> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="repost" v-if="isRepost"> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> | ||||
| 				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<router-link class="name" :href="`/@${acct}`">{{ getUserName(post.user) }}</router-link> | ||||
| 			がRepost | ||||
| 			<router-link class="name" :href="`/@${acct}`">{{ getUserName(note.user) }}</router-link> | ||||
| 			がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
|  | @ -38,28 +38,28 @@ | |||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-post-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> | ||||
| 			<mk-note-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> | ||||
| 			<div class="media" v-if="p.media.length > 0"> | ||||
| 				<mk-media-list :media-list="p.media"/> | ||||
| 			</div> | ||||
| 			<mk-poll v-if="p.poll" :post="p"/> | ||||
| 			<mk-poll v-if="p.poll" :note="p"/> | ||||
| 			<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| 			<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 				<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 			</div> | ||||
| 			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 			<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 			<div class="repost" v-if="p.repost"> | ||||
| 				<mk-post-preview :post="p.repost"/> | ||||
| 			<div class="renote" v-if="p.renote"> | ||||
| 				<mk-note-preview :note="p.renote"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<footer> | ||||
| 			<mk-reactions-viewer :post="p"/> | ||||
| 			<mk-reactions-viewer :note="p"/> | ||||
| 			<button @click="reply" title="返信"> | ||||
| 				%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 			</button> | ||||
| 			<button @click="repost" title="Repost"> | ||||
| 				%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> | ||||
| 			<button @click="renote" title="Renote"> | ||||
| 				%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 			</button> | ||||
| 			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="リアクション"> | ||||
| 				%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
|  | @ -70,7 +70,7 @@ | |||
| 		</footer> | ||||
| 	</article> | ||||
| 	<div class="replies" v-if="!compact"> | ||||
| 		<x-sub v-for="post in replies" :key="post.id" :post="post"/> | ||||
| 		<x-sub v-for="note in replies" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -83,10 +83,10 @@ import getUserName from '../../../../../renderers/get-user-name'; | |||
| import parse from '../../../../../text/parse'; | ||||
| 
 | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRepostFormWindow from './repost-form-window.vue'; | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkRenoteFormWindow from './renote-form-window.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './post-detail.sub.vue'; | ||||
| import XSub from './note-detail.sub.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
|  | @ -94,7 +94,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		post: { | ||||
| 		note: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
|  | @ -112,14 +112,14 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		isRepost(): boolean { | ||||
| 			return (this.post.repost && | ||||
| 				this.post.text == null && | ||||
| 				this.post.mediaIds == null && | ||||
| 				this.post.poll == null); | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds == null && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRepost ? this.post.repost : this.post; | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
|  | @ -132,10 +132,10 @@ export default Vue.extend({ | |||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
|  | @ -158,8 +158,8 @@ export default Vue.extend({ | |||
| 	mounted() { | ||||
| 		// Get replies | ||||
| 		if (!this.compact) { | ||||
| 			(this as any).api('posts/replies', { | ||||
| 				postId: this.p.id, | ||||
| 			(this as any).api('notes/replies', { | ||||
| 				noteId: this.p.id, | ||||
| 				limit: 8 | ||||
| 			}).then(replies => { | ||||
| 				this.replies = replies; | ||||
|  | @ -190,8 +190,8 @@ export default Vue.extend({ | |||
| 			this.contextFetching = true; | ||||
| 
 | ||||
| 			// Fetch context | ||||
| 			(this as any).api('posts/context', { | ||||
| 				postId: this.p.replyId | ||||
| 			(this as any).api('notes/context', { | ||||
| 				noteId: this.p.replyId | ||||
| 			}).then(context => { | ||||
| 				this.contextFetching = false; | ||||
| 				this.context = context.reverse(); | ||||
|  | @ -202,21 +202,21 @@ export default Vue.extend({ | |||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		repost() { | ||||
| 			(this as any).os.new(MkRepostFormWindow, { | ||||
| 				post: this.p | ||||
| 		renote() { | ||||
| 			(this as any).os.new(MkRenoteFormWindow, { | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				post: this.p | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkPostMenu, { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				post: this.p | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -226,7 +226,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .mk-post-detail | ||||
| .mk-note-detail | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	overflow hidden | ||||
|  | @ -263,7 +263,7 @@ export default Vue.extend({ | |||
| 		> * | ||||
| 			border-bottom 1px solid #eef0f2 | ||||
| 
 | ||||
| 	> .repost | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  | @ -355,10 +355,10 @@ export default Vue.extend({ | |||
| 		> .body | ||||
| 			padding 8px 0 | ||||
| 
 | ||||
| 			> .repost | ||||
| 			> .renote | ||||
| 				margin 8px 0 | ||||
| 
 | ||||
| 				> .mk-post-preview | ||||
| 				> .mk-note-preview | ||||
| 					padding 16px | ||||
| 					border dashed 1px #c0dac6 | ||||
| 					border-radius 8px | ||||
|  |  | |||
|  | @ -2,13 +2,13 @@ | |||
| <mk-window ref="window" is-modal @closed="$destroy"> | ||||
| 	<span slot="header"> | ||||
| 		<span :class="$style.icon" v-if="geo">%fa:map-marker-alt%</span> | ||||
| 		<span v-if="!reply">%i18n:desktop.tags.mk-post-form-window.post%</span> | ||||
| 		<span v-if="!reply">%i18n:desktop.tags.mk-post-form-window.note%</span> | ||||
| 		<span v-if="reply">%i18n:desktop.tags.mk-post-form-window.reply%</span> | ||||
| 		<span :class="$style.count" v-if="media.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.attaches%'.replace('{}', media.length) }}</span> | ||||
| 		<span :class="$style.count" v-if="uploadings.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span> | ||||
| 	</span> | ||||
| 
 | ||||
| 	<mk-post-preview v-if="reply" :class="$style.postPreview" :post="reply"/> | ||||
| 	<mk-note-preview v-if="reply" :class="$style.notePreview" :note="reply"/> | ||||
| 	<mk-post-form ref="form" | ||||
| 		:reply="reply" | ||||
| 		@posted="onPosted" | ||||
|  | @ -70,7 +70,7 @@ export default Vue.extend({ | |||
| 	&:after | ||||
| 		content ')' | ||||
| 
 | ||||
| .postPreview | ||||
| .notePreview | ||||
| 	margin 16px 22px | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XDraggable | ||||
| 	}, | ||||
| 	props: ['reply', 'repost'], | ||||
| 	props: ['reply', 'renote'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			posting: false, | ||||
|  | @ -61,28 +61,28 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	computed: { | ||||
| 		draftId(): string { | ||||
| 			return this.repost | ||||
| 				? 'repost:' + this.repost.id | ||||
| 			return this.renote | ||||
| 				? 'renote:' + this.renote.id | ||||
| 				: this.reply | ||||
| 					? 'reply:' + this.reply.id | ||||
| 					: 'post'; | ||||
| 					: 'note'; | ||||
| 		}, | ||||
| 		placeholder(): string { | ||||
| 			return this.repost | ||||
| 			return this.renote | ||||
| 				? '%i18n:desktop.tags.mk-post-form.quote-placeholder%' | ||||
| 				: this.reply | ||||
| 					? '%i18n:desktop.tags.mk-post-form.reply-placeholder%' | ||||
| 					: '%i18n:desktop.tags.mk-post-form.post-placeholder%'; | ||||
| 					: '%i18n:desktop.tags.mk-post-form.note-placeholder%'; | ||||
| 		}, | ||||
| 		submitText(): string { | ||||
| 			return this.repost | ||||
| 				? '%i18n:desktop.tags.mk-post-form.repost%' | ||||
| 			return this.renote | ||||
| 				? '%i18n:desktop.tags.mk-post-form.renote%' | ||||
| 				: this.reply | ||||
| 					? '%i18n:desktop.tags.mk-post-form.reply%' | ||||
| 					: '%i18n:desktop.tags.mk-post-form.post%'; | ||||
| 					: '%i18n:desktop.tags.mk-post-form.note%'; | ||||
| 		}, | ||||
| 		canPost(): boolean { | ||||
| 			return !this.posting && (this.text.length != 0 || this.files.length != 0 || this.poll || this.repost); | ||||
| 			return !this.posting && (this.text.length != 0 || this.files.length != 0 || this.poll || this.renote); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
|  | @ -217,11 +217,11 @@ export default Vue.extend({ | |||
| 		post() { | ||||
| 			this.posting = true; | ||||
| 
 | ||||
| 			(this as any).api('posts/create', { | ||||
| 			(this as any).api('notes/create', { | ||||
| 				text: this.text == '' ? undefined : this.text, | ||||
| 				mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, | ||||
| 				replyId: this.reply ? this.reply.id : undefined, | ||||
| 				repostId: this.repost ? this.repost.id : undefined, | ||||
| 				renoteId: this.renote ? this.renote.id : undefined, | ||||
| 				poll: this.poll ? (this.$refs.poll as any).get() : undefined, | ||||
| 				geo: this.geo ? { | ||||
| 					coordinates: [this.geo.longitude, this.geo.latitude], | ||||
|  | @ -235,17 +235,17 @@ export default Vue.extend({ | |||
| 				this.clear(); | ||||
| 				this.deleteDraft(); | ||||
| 				this.$emit('posted'); | ||||
| 				(this as any).apis.notify(this.repost | ||||
| 				(this as any).apis.notify(this.renote | ||||
| 					? '%i18n:desktop.tags.mk-post-form.reposted%' | ||||
| 					: this.reply | ||||
| 						? '%i18n:desktop.tags.mk-post-form.replied%' | ||||
| 						: '%i18n:desktop.tags.mk-post-form.posted%'); | ||||
| 			}).catch(err => { | ||||
| 				(this as any).apis.notify(this.repost | ||||
| 					? '%i18n:desktop.tags.mk-post-form.repost-failed%' | ||||
| 				(this as any).apis.notify(this.renote | ||||
| 					? '%i18n:desktop.tags.mk-post-form.renote-failed%' | ||||
| 					: this.reply | ||||
| 						? '%i18n:desktop.tags.mk-post-form.reply-failed%' | ||||
| 						: '%i18n:desktop.tags.mk-post-form.post-failed%'); | ||||
| 						: '%i18n:desktop.tags.mk-post-form.note-failed%'); | ||||
| 			}).then(() => { | ||||
| 				this.posting = false; | ||||
| 			}); | ||||
|  |  | |||
|  | @ -1,19 +1,19 @@ | |||
| <template> | ||||
| <div class="post" tabindex="-1" :title="title" @keydown="onKeydown"> | ||||
| <div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :post="p.reply"/> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="repost" v-if="isRepost"> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="post.userId"> | ||||
| 				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</a> | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<a class="name" :href="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a> | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 		</p> | ||||
| 		<mk-time :time="post.createdAt"/> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
|  | @ -38,38 +38,38 @@ | |||
| 				</p> | ||||
| 				<div class="text"> | ||||
| 					<a class="reply" v-if="p.reply">%fa:reply%</a> | ||||
| 					<mk-post-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.repost">RP:</a> | ||||
| 					<mk-note-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.renote">RP:</a> | ||||
| 				</div> | ||||
| 				<div class="media" v-if="p.media.length > 0"> | ||||
| 					<mk-media-list :media-list="p.media"/> | ||||
| 				</div> | ||||
| 				<mk-poll v-if="p.poll" :post="p" ref="pollViewer"/> | ||||
| 				<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> | ||||
| 				<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 					<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 				</div> | ||||
| 				<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 				<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 				<div class="repost" v-if="p.repost"> | ||||
| 					<mk-post-preview :post="p.repost"/> | ||||
| 				<div class="renote" v-if="p.renote"> | ||||
| 					<mk-note-preview :note="p.renote"/> | ||||
| 				</div> | ||||
| 				<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| 			</div> | ||||
| 			<footer> | ||||
| 				<mk-reactions-viewer :post="p" ref="reactionsViewer"/> | ||||
| 				<button @click="reply" title="%i18n:desktop.tags.mk-timeline-post.reply%"> | ||||
| 				<mk-reactions-viewer :note="p" ref="reactionsViewer"/> | ||||
| 				<button @click="reply" title="%i18n:desktop.tags.mk-timeline-note.reply%"> | ||||
| 					%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 				</button> | ||||
| 				<button @click="repost" title="%i18n:desktop.tags.mk-timeline-post.repost%"> | ||||
| 					%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> | ||||
| 				<button @click="renote" title="%i18n:desktop.tags.mk-timeline-note.renote%"> | ||||
| 					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 				</button> | ||||
| 				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%"> | ||||
| 				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-note.add-reaction%"> | ||||
| 					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
| 				</button> | ||||
| 				<button @click="menu" ref="menuButton"> | ||||
| 					%fa:ellipsis-h% | ||||
| 				</button> | ||||
| 				<button title="%i18n:desktop.tags.mk-timeline-post.detail"> | ||||
| 				<button title="%i18n:desktop.tags.mk-timeline-note.detail"> | ||||
| 					<template v-if="!isDetailOpened">%fa:caret-down%</template> | ||||
| 					<template v-if="isDetailOpened">%fa:caret-up%</template> | ||||
| 				</button> | ||||
|  | @ -77,7 +77,7 @@ | |||
| 		</div> | ||||
| 	</article> | ||||
| 	<div class="detail" v-if="isDetailOpened"> | ||||
| 		<mk-post-status-graph width="462" height="130" :post="p"/> | ||||
| 		<mk-note-status-graph width="462" height="130" :note="p"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -90,10 +90,10 @@ import getUserName from '../../../../../renderers/get-user-name'; | |||
| import parse from '../../../../../text/parse'; | ||||
| 
 | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRepostFormWindow from './repost-form-window.vue'; | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkRenoteFormWindow from './renote-form-window.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './posts.post.sub.vue'; | ||||
| import XSub from './notes.note.sub.vue'; | ||||
| 
 | ||||
| function focus(el, fn) { | ||||
| 	const target = fn(el); | ||||
|  | @ -111,7 +111,7 @@ export default Vue.extend({ | |||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
|  | @ -128,14 +128,14 @@ export default Vue.extend({ | |||
| 		name(): string { | ||||
| 			return getUserName(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
| 			return (this.post.repost && | ||||
| 				this.post.text == null && | ||||
| 				this.post.mediaIds == null && | ||||
| 				this.post.poll == null); | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds == null && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRepost ? this.post.repost : this.post; | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
|  | @ -211,7 +211,7 @@ export default Vue.extend({ | |||
| 					type: 'capture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated); | ||||
| 				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		decapture(withHandler = false) { | ||||
|  | @ -220,18 +220,18 @@ export default Vue.extend({ | |||
| 					type: 'decapture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); | ||||
| 				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		onStreamConnected() { | ||||
| 			this.capture(); | ||||
| 		}, | ||||
| 		onStreamPostUpdated(data) { | ||||
| 			const post = data.post; | ||||
| 			if (post.id == this.post.id) { | ||||
| 				this.$emit('update:post', post); | ||||
| 			} else if (post.id == this.post.repostId) { | ||||
| 				this.post.repost = post; | ||||
| 		onStreamNoteUpdated(data) { | ||||
| 			const note = data.note; | ||||
| 			if (note.id == this.note.id) { | ||||
| 				this.$emit('update:note', note); | ||||
| 			} else if (note.id == this.note.renoteId) { | ||||
| 				this.note.renote = note; | ||||
| 			} | ||||
| 		}, | ||||
| 		reply() { | ||||
|  | @ -239,21 +239,21 @@ export default Vue.extend({ | |||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		repost() { | ||||
| 			(this as any).os.new(MkRepostFormWindow, { | ||||
| 				post: this.p | ||||
| 		renote() { | ||||
| 			(this as any).os.new(MkRenoteFormWindow, { | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				post: this.p | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkPostMenu, { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				post: this.p | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		onKeydown(e) { | ||||
|  | @ -274,7 +274,7 @@ export default Vue.extend({ | |||
| 
 | ||||
| 				case e.which == 81: // [q] | ||||
| 				case e.which == 69: // [e] | ||||
| 					this.repost(); | ||||
| 					this.renote(); | ||||
| 					break; | ||||
| 
 | ||||
| 				case e.which == 70: // [f] | ||||
|  | @ -299,7 +299,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .post | ||||
| .note | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	background #fff | ||||
|  | @ -309,7 +309,7 @@ export default Vue.extend({ | |||
| 		border-top-left-radius 6px | ||||
| 		border-top-right-radius 6px | ||||
| 
 | ||||
| 		> .repost | ||||
| 		> .renote | ||||
| 			border-top-left-radius 6px | ||||
| 			border-top-right-radius 6px | ||||
| 
 | ||||
|  | @ -330,7 +330,7 @@ export default Vue.extend({ | |||
| 			border 2px solid rgba($theme-color, 0.3) | ||||
| 			border-radius 4px | ||||
| 
 | ||||
| 	> .repost | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  | @ -369,7 +369,7 @@ export default Vue.extend({ | |||
| 		padding 0 16px | ||||
| 		background rgba(0, 0, 0, 0.0125) | ||||
| 
 | ||||
| 		> .mk-post-preview | ||||
| 		> .mk-note-preview | ||||
| 			background transparent | ||||
| 
 | ||||
| 	> article | ||||
|  | @ -529,10 +529,10 @@ export default Vue.extend({ | |||
| 				> .mk-poll | ||||
| 					font-size 80% | ||||
| 
 | ||||
| 				> .repost | ||||
| 				> .renote | ||||
| 					margin 8px 0 | ||||
| 
 | ||||
| 					> .mk-post-preview | ||||
| 					> .mk-note-preview | ||||
| 						padding 16px | ||||
| 						border dashed 1px #c0dac6 | ||||
| 						border-radius 8px | ||||
|  |  | |||
|  | @ -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> | ||||
							
								
								
									
										131
									
								
								src/client/app/desktop/views/components/renote-form.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/client/app/desktop/views/components/renote-form.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,7 +1,7 @@ | |||
| <template> | ||||
| <mk-window ref="window" is-modal @closed="$destroy"> | ||||
| 	<span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-repost-form-window.title%</span> | ||||
| 	<mk-repost-form ref="form" :post="post" @posted="onPosted" @canceled="onCanceled"/> | ||||
| 	<span slot="header" :class="$style.header">%fa:retweet%%i18n:desktop.tags.mk-renote-form-window.title%</span> | ||||
| 	<mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/> | ||||
| </mk-window> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -9,7 +9,7 @@ | |||
| import Vue from 'vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	mounted() { | ||||
| 		document.addEventListener('keydown', this.onDocumentKeydown); | ||||
| 	}, | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| <template> | ||||
| <div class="mk-repost-form"> | ||||
| 	<mk-post-preview :post="post"/> | ||||
| <div class="mk-renote-form"> | ||||
| 	<mk-note-preview :note="note"/> | ||||
| 	<template v-if="!quote"> | ||||
| 		<footer> | ||||
| 			<a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-repost-form.quote%</a> | ||||
| 			<button class="cancel" @click="cancel">%i18n:desktop.tags.mk-repost-form.cancel%</button> | ||||
| 			<button class="ok" @click="ok" :disabled="wait">{{ wait ? '%i18n:desktop.tags.mk-repost-form.reposting%' : '%i18n:desktop.tags.mk-repost-form.repost%' }}</button> | ||||
| 			<a class="quote" v-if="!quote" @click="onQuote">%i18n:desktop.tags.mk-renote-form.quote%</a> | ||||
| 			<button class="cancel" @click="cancel">%i18n:desktop.tags.mk-renote-form.cancel%</button> | ||||
| 			<button class="ok" @click="ok" :disabled="wait">{{ wait ? '%i18n:desktop.tags.mk-renote-form.reposting%' : '%i18n:desktop.tags.mk-renote-form.renote%' }}</button> | ||||
| 		</footer> | ||||
| 	</template> | ||||
| 	<template v-if="quote"> | ||||
| 		<mk-post-form ref="form" :repost="post" @posted="onChildFormPosted"/> | ||||
| 		<mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/> | ||||
| 	</template> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -18,7 +18,7 @@ | |||
| import Vue from 'vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			wait: false, | ||||
|  | @ -28,13 +28,13 @@ export default Vue.extend({ | |||
| 	methods: { | ||||
| 		ok() { | ||||
| 			this.wait = true; | ||||
| 			(this as any).api('posts/create', { | ||||
| 				repostId: this.post.id | ||||
| 			(this as any).api('notes/create', { | ||||
| 				renoteId: this.note.id | ||||
| 			}).then(data => { | ||||
| 				this.$emit('posted'); | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.success%'); | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.success%'); | ||||
| 			}).catch(err => { | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.failure%'); | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-renote-form.failure%'); | ||||
| 			}).then(() => { | ||||
| 				this.wait = false; | ||||
| 			}); | ||||
|  | @ -59,9 +59,9 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .mk-repost-form | ||||
| .mk-renote-form | ||||
| 
 | ||||
| 	> .mk-post-preview | ||||
| 	> .mk-note-preview | ||||
| 		margin 16px 22px | ||||
| 
 | ||||
| 	> footer | ||||
|  |  | |||
							
								
								
									
										44
									
								
								src/client/app/desktop/views/components/sub-note-content.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/client/app/desktop/views/components/sub-note-content.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -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> | ||||
|  | @ -4,15 +4,15 @@ | |||
| 	<div class="fetching" v-if="fetching"> | ||||
| 		<mk-ellipsis-icon/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="posts.length == 0 && !fetching"> | ||||
| 	<p class="empty" v-if="notes.length == 0 && !fetching"> | ||||
| 		%fa:R comments%自分の投稿や、自分がフォローしているユーザーの投稿が表示されます。 | ||||
| 	</p> | ||||
| 	<mk-posts :posts="posts" ref="timeline"> | ||||
| 	<mk-notes :notes="notes" ref="timeline"> | ||||
| 		<button slot="footer" @click="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">もっと見る</template> | ||||
| 			<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> | ||||
| 		</button> | ||||
| 	</mk-posts> | ||||
| 	</mk-notes> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -26,7 +26,7 @@ export default Vue.extend({ | |||
| 			fetching: true, | ||||
| 			moreFetching: false, | ||||
| 			existMore: false, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			date: null | ||||
|  | @ -41,7 +41,7 @@ export default Vue.extend({ | |||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('post', this.onPost); | ||||
| 		this.connection.on('note', this.onNote); | ||||
| 		this.connection.on('follow', this.onChangeFollowing); | ||||
| 		this.connection.on('unfollow', this.onChangeFollowing); | ||||
| 
 | ||||
|  | @ -51,7 +51,7 @@ export default Vue.extend({ | |||
| 		this.fetch(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('post', this.onPost); | ||||
| 		this.connection.off('note', this.onNote); | ||||
| 		this.connection.off('follow', this.onChangeFollowing); | ||||
| 		this.connection.off('unfollow', this.onChangeFollowing); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
|  | @ -63,45 +63,45 @@ export default Vue.extend({ | |||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('posts/timeline', { | ||||
| 			(this as any).api('notes/timeline', { | ||||
| 				limit: 11, | ||||
| 				untilDate: this.date ? this.date.getTime() : undefined | ||||
| 			}).then(posts => { | ||||
| 				if (posts.length == 11) { | ||||
| 					posts.pop(); | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == 11) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				this.posts = posts; | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				this.$emit('loaded'); | ||||
| 				if (cb) cb(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			if (this.moreFetching || this.fetching || this.posts.length == 0 || !this.existMore) return; | ||||
| 			if (this.moreFetching || this.fetching || this.notes.length == 0 || !this.existMore) return; | ||||
| 			this.moreFetching = true; | ||||
| 			(this as any).api('posts/timeline', { | ||||
| 			(this as any).api('notes/timeline', { | ||||
| 				limit: 11, | ||||
| 				untilId: this.posts[this.posts.length - 1].id | ||||
| 			}).then(posts => { | ||||
| 				if (posts.length == 11) { | ||||
| 					posts.pop(); | ||||
| 				untilId: this.notes[this.notes.length - 1].id | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == 11) { | ||||
| 					notes.pop(); | ||||
| 				} else { | ||||
| 					this.existMore = false; | ||||
| 				} | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		}, | ||||
| 		onPost(post) { | ||||
| 		onNote(note) { | ||||
| 			// サウンドを再生する | ||||
| 			if ((this as any).os.isEnableSounds) { | ||||
| 				const sound = new Audio(`${url}/assets/post.mp3`); | ||||
| 				const sound = new Audio(`${url}/assets/note.mp3`); | ||||
| 				sound.volume = localStorage.getItem('soundVolume') ? parseInt(localStorage.getItem('soundVolume'), 10) / 100 : 1; | ||||
| 				sound.play(); | ||||
| 			} | ||||
| 
 | ||||
| 			this.posts.unshift(post); | ||||
| 			this.notes.unshift(note); | ||||
| 		}, | ||||
| 		onChangeFollowing() { | ||||
| 			this.fetch(); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <div class="post"> | ||||
| 	<button @click="post" title="%i18n:desktop.tags.mk-ui-header-post-button.post%">%fa:pencil-alt%</button> | ||||
| <div class="note"> | ||||
| 	<button @click="post" title="%i18n:desktop.tags.mk-ui-header-note-button.note%">%fa:pencil-alt%</button> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -19,7 +19,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .post | ||||
| .note | ||||
| 	display inline-block | ||||
| 	padding 8px | ||||
| 	height 100% | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| 		<div class="description">{{ u.description }}</div> | ||||
| 		<div class="status"> | ||||
| 			<div> | ||||
| 				<p>投稿</p><a>{{ u.postsCount }}</a> | ||||
| 				<p>投稿</p><a>{{ u.notesCount }}</a> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<p>フォロー</p><a>{{ u.followingCount }}</a> | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import getPostSummary from '../../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
|  | @ -29,13 +29,13 @@ export default Vue.extend({ | |||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('post', this.onStreamPost); | ||||
| 		this.connection.on('note', this.onStreamNote); | ||||
| 		document.addEventListener('visibilitychange', this.onVisibilitychange, false); | ||||
| 
 | ||||
| 		Progress.start(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('post', this.onStreamPost); | ||||
| 		this.connection.off('note', this.onStreamNote); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
| 		document.removeEventListener('visibilitychange', this.onVisibilitychange); | ||||
| 	}, | ||||
|  | @ -44,10 +44,10 @@ export default Vue.extend({ | |||
| 			Progress.done(); | ||||
| 		}, | ||||
| 
 | ||||
| 		onStreamPost(post) { | ||||
| 			if (document.hidden && post.userId != (this as any).os.i.id) { | ||||
| 		onStreamNote(note) { | ||||
| 			if (document.hidden && note.userId != (this as any).os.i.id) { | ||||
| 				this.unreadCount++; | ||||
| 				document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; | ||||
| 				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| <template> | ||||
| <mk-ui> | ||||
| 	<main v-if="!fetching"> | ||||
| 		<a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:desktop.tags.mk-post-page.next%</a> | ||||
| 		<mk-post-detail :post="post"/> | ||||
| 		<a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:desktop.tags.mk-post-page.prev%</a> | ||||
| 		<a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:desktop.tags.mk-note-page.next%</a> | ||||
| 		<mk-note-detail :note="note"/> | ||||
| 		<a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:desktop.tags.mk-note-page.prev%</a> | ||||
| 	</main> | ||||
| </mk-ui> | ||||
| </template> | ||||
|  | @ -16,7 +16,7 @@ export default Vue.extend({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			post: null | ||||
| 			note: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
|  | @ -30,10 +30,10 @@ export default Vue.extend({ | |||
| 			Progress.start(); | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('posts/show', { | ||||
| 				postId: this.$route.params.post | ||||
| 			}).then(post => { | ||||
| 				this.post = post; | ||||
| 			(this as any).api('notes/show', { | ||||
| 				noteId: this.$route.params.note | ||||
| 			}).then(note => { | ||||
| 				this.note = note; | ||||
| 				this.fetching = false; | ||||
| 
 | ||||
| 				Progress.done(); | ||||
|  | @ -60,7 +60,7 @@ main | |||
| 		> [data-fa] | ||||
| 			margin-right 4px | ||||
| 
 | ||||
| 	> .mk-post-detail | ||||
| 	> .mk-note-detail | ||||
| 		margin 0 auto | ||||
| 		width 640px | ||||
| 
 | ||||
|  | @ -7,12 +7,12 @@ | |||
| 		<mk-ellipsis-icon/> | ||||
| 	</div> | ||||
| 	<p :class="$style.empty" v-if="!fetching && empty">%fa:search%「{{ q }}」に関する投稿は見つかりませんでした。</p> | ||||
| 	<mk-posts ref="timeline" :class="$style.posts" :posts="posts"> | ||||
| 	<mk-notes ref="timeline" :class="$style.notes" :notes="notes"> | ||||
| 		<div slot="footer"> | ||||
| 			<template v-if="!moreFetching">%fa:search%</template> | ||||
| 			<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> | ||||
| 		</div> | ||||
| 	</mk-posts> | ||||
| 	</mk-notes> | ||||
| </mk-ui> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -30,7 +30,7 @@ export default Vue.extend({ | |||
| 			moreFetching: false, | ||||
| 			existMore: false, | ||||
| 			offset: 0, | ||||
| 			posts: [] | ||||
| 			notes: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
|  | @ -38,7 +38,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	computed: { | ||||
| 		empty(): boolean { | ||||
| 			return this.posts.length == 0; | ||||
| 			return this.notes.length == 0; | ||||
| 		}, | ||||
| 		q(): string { | ||||
| 			return this.$route.query.q; | ||||
|  | @ -66,33 +66,33 @@ export default Vue.extend({ | |||
| 			this.fetching = true; | ||||
| 			Progress.start(); | ||||
| 
 | ||||
| 			(this as any).api('posts/search', Object.assign({ | ||||
| 			(this as any).api('notes/search', Object.assign({ | ||||
| 				limit: limit + 1, | ||||
| 				offset: this.offset | ||||
| 			}, parse(this.q))).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 			}, parse(this.q))).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				this.posts = posts; | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				Progress.done(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			if (this.moreFetching || this.fetching || this.posts.length == 0 || !this.existMore) return; | ||||
| 			if (this.moreFetching || this.fetching || this.notes.length == 0 || !this.existMore) return; | ||||
| 			this.offset += limit; | ||||
| 			this.moreFetching = true; | ||||
| 			return (this as any).api('posts/search', Object.assign({ | ||||
| 			return (this as any).api('notes/search', Object.assign({ | ||||
| 				limit: limit + 1, | ||||
| 				offset: this.offset | ||||
| 			}, parse(this.q))).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 			}, parse(this.q))).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 				} else { | ||||
| 					this.existMore = false; | ||||
| 				} | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		}, | ||||
|  | @ -111,7 +111,7 @@ export default Vue.extend({ | |||
| 	margin 0 auto | ||||
| 	color #555 | ||||
| 
 | ||||
| .posts | ||||
| .notes | ||||
| 	max-width 600px | ||||
| 	margin 0 auto | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 		</div> | ||||
| 	</div> | ||||
| 	<main> | ||||
| 		<mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/> | ||||
| 		<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> | ||||
| 		<x-timeline class="timeline" ref="tl" :user="user"/> | ||||
| 	</main> | ||||
| 	<div> | ||||
|  |  | |||
|  | @ -22,13 +22,13 @@ export default Vue.extend({ | |||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/posts', { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withMedia: true, | ||||
| 			limit: 9 | ||||
| 		}).then(posts => { | ||||
| 			posts.forEach(post => { | ||||
| 				post.media.forEach(media => { | ||||
| 		}).then(notes => { | ||||
| 			notes.forEach(note => { | ||||
| 				note.media.forEach(media => { | ||||
| 					if (this.images.length < 9) this.images.push(media); | ||||
| 				}); | ||||
| 			}); | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
| 		<p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screenName}`" target="_blank">@{{ user.account.twitter.screenName }}</a></p> | ||||
| 	</div> | ||||
| 	<div class="status"> | ||||
| 		<p class="posts-count">%fa:angle-right%<a>{{ user.postsCount }}</a><b>投稿</b></p> | ||||
| 		<p class="notes-count">%fa:angle-right%<a>{{ user.notesCount }}</a><b>投稿</b></p> | ||||
| 		<p class="following">%fa:angle-right%<a @click="showFollowing">{{ user.followingCount }}</a>人を<b>フォロー</b></p> | ||||
| 		<p class="followers">%fa:angle-right%<a @click="showFollowers">{{ user.followersCount }}</a>人の<b>フォロワー</b></p> | ||||
| 	</div> | ||||
|  |  | |||
|  | @ -8,12 +8,12 @@ | |||
| 		<mk-ellipsis-icon/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="empty">%fa:R comments%このユーザーはまだ何も投稿していないようです。</p> | ||||
| 	<mk-posts ref="timeline" :posts="posts"> | ||||
| 	<mk-notes ref="timeline" :notes="notes"> | ||||
| 		<div slot="footer"> | ||||
| 			<template v-if="!moreFetching">%fa:moon%</template> | ||||
| 			<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> | ||||
| 		</div> | ||||
| 	</mk-posts> | ||||
| 	</mk-notes> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 			moreFetching: false, | ||||
| 			mode: 'default', | ||||
| 			unreadCount: 0, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			date: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -38,7 +38,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	computed: { | ||||
| 		empty(): boolean { | ||||
| 			return this.posts.length == 0; | ||||
| 			return this.notes.length == 0; | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
|  | @ -60,26 +60,26 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		}, | ||||
| 		fetch(cb?) { | ||||
| 			(this as any).api('users/posts', { | ||||
| 			(this as any).api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				untilDate: this.date ? this.date.getTime() : undefined, | ||||
| 				with_replies: this.mode == 'with-replies' | ||||
| 			}).then(posts => { | ||||
| 				this.posts = posts; | ||||
| 			}).then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				if (cb) cb(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			if (this.moreFetching || this.fetching || this.posts.length == 0) return; | ||||
| 			if (this.moreFetching || this.fetching || this.notes.length == 0) return; | ||||
| 			this.moreFetching = true; | ||||
| 			(this as any).api('users/posts', { | ||||
| 			(this as any).api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				with_replies: this.mode == 'with-replies', | ||||
| 				untilId: this.posts[this.posts.length - 1].id | ||||
| 			}).then(posts => { | ||||
| 				untilId: this.notes[this.notes.length - 1].id | ||||
| 			}).then(notes => { | ||||
| 				this.moreFetching = false; | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 			}); | ||||
| 		}, | ||||
| 		onScroll() { | ||||
|  |  | |||
|  | @ -24,11 +24,11 @@ export default Vue.extend({ | |||
| 
 | ||||
| 			if (/^>>([0-9]+) /.test(this.text)) { | ||||
| 				const index = this.text.match(/^>>([0-9]+) /)[1]; | ||||
| 				reply = (this.$parent as any).posts.find(p => p.index.toString() == index); | ||||
| 				reply = (this.$parent as any).notes.find(p => p.index.toString() == index); | ||||
| 				this.text = this.text.replace(/^>>([0-9]+) /, ''); | ||||
| 			} | ||||
| 
 | ||||
| 			(this as any).api('posts/create', { | ||||
| 			(this as any).api('notes/create', { | ||||
| 				text: this.text, | ||||
| 				replyId: reply ? reply.id : undefined, | ||||
| 				channelId: (this.$parent as any).channel.id | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| <template> | ||||
| <div class="post"> | ||||
| <div class="note"> | ||||
| 	<header> | ||||
| 		<a class="index" @click="reply">{{ post.index }}:</a> | ||||
| 		<router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ name }}</b></router-link> | ||||
| 		<a class="index" @click="reply">{{ note.index }}:</a> | ||||
| 		<router-link class="name" :to="`/@${acct}`" v-user-preview="note.user.id"><b>{{ name }}</b></router-link> | ||||
| 		<span>ID:<i>{{ acct }}</i></span> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<a v-if="post.reply">>>{{ post.reply.index }}</a> | ||||
| 		{{ post.text }} | ||||
| 		<div class="media" v-if="post.media"> | ||||
| 			<a v-for="file in post.media" :href="file.url" target="_blank"> | ||||
| 		<a v-if="note.reply">>>{{ note.reply.index }}</a> | ||||
| 		{{ note.text }} | ||||
| 		<div class="media" v-if="note.media"> | ||||
| 			<a v-for="file in note.media" :href="file.url" target="_blank"> | ||||
| 				<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/> | ||||
| 			</a> | ||||
| 		</div> | ||||
|  | @ -23,25 +23,25 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		reply() { | ||||
| 			this.$emit('reply', this.post); | ||||
| 			this.$emit('reply', this.note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .post | ||||
| .note | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	color #444 | ||||
|  | @ -1,9 +1,9 @@ | |||
| <template> | ||||
| <div class="channel"> | ||||
| 	<p v-if="fetching">読み込み中<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching" ref="posts" class="posts"> | ||||
| 		<p v-if="posts.length == 0">まだ投稿がありません</p> | ||||
| 		<x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/> | ||||
| 	<div v-if="!fetching" ref="notes" class="notes"> | ||||
| 		<p v-if="notes.length == 0">まだ投稿がありません</p> | ||||
| 		<x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.id" @reply="reply"/> | ||||
| 	</div> | ||||
| 	<x-form class="form" ref="form"/> | ||||
| </div> | ||||
|  | @ -13,18 +13,18 @@ | |||
| import Vue from 'vue'; | ||||
| import ChannelStream from '../../../common/scripts/streaming/channel'; | ||||
| import XForm from './channel.channel.form.vue'; | ||||
| import XPost from './channel.channel.post.vue'; | ||||
| import XNote from './channel.channel.note.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XForm, | ||||
| 		XPost | ||||
| 		XNote | ||||
| 	}, | ||||
| 	props: ['channel'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			connection: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -43,10 +43,10 @@ export default Vue.extend({ | |||
| 		zap() { | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('channels/posts', { | ||||
| 			(this as any).api('channels/notes', { | ||||
| 				channelId: this.channel.id | ||||
| 			}).then(posts => { | ||||
| 				this.posts = posts; | ||||
| 			}).then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 
 | ||||
| 				this.$nextTick(() => { | ||||
|  | @ -55,24 +55,24 @@ export default Vue.extend({ | |||
| 
 | ||||
| 				this.disconnect(); | ||||
| 				this.connection = new ChannelStream((this as any).os, this.channel.id); | ||||
| 				this.connection.on('post', this.onPost); | ||||
| 				this.connection.on('note', this.onNote); | ||||
| 			}); | ||||
| 		}, | ||||
| 		disconnect() { | ||||
| 			if (this.connection) { | ||||
| 				this.connection.off('post', this.onPost); | ||||
| 				this.connection.off('note', this.onNote); | ||||
| 				this.connection.close(); | ||||
| 			} | ||||
| 		}, | ||||
| 		onPost(post) { | ||||
| 			this.posts.unshift(post); | ||||
| 		onNote(note) { | ||||
| 			this.notes.unshift(note); | ||||
| 			this.scrollToBottom(); | ||||
| 		}, | ||||
| 		scrollToBottom() { | ||||
| 			(this.$refs.posts as any).scrollTop = (this.$refs.posts as any).scrollHeight; | ||||
| 			(this.$refs.notes as any).scrollTop = (this.$refs.notes as any).scrollHeight; | ||||
| 		}, | ||||
| 		reply(post) { | ||||
| 			(this.$refs.form as any).text = `>>${ post.index } `; | ||||
| 		reply(note) { | ||||
| 			(this.$refs.form as any).text = `>>${ note.index } `; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | @ -87,12 +87,12 @@ export default Vue.extend({ | |||
| 		text-align center | ||||
| 		color #aaa | ||||
| 
 | ||||
| 	> .posts | ||||
| 	> .notes | ||||
| 		height calc(100% - 38px) | ||||
| 		overflow auto | ||||
| 		font-size 0.9em | ||||
| 
 | ||||
| 		> .post | ||||
| 		> .note | ||||
| 			border-bottom solid 1px #eee | ||||
| 
 | ||||
| 			&:last-child | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
| 	<div class="poll" v-if="!fetching && poll != null"> | ||||
| 		<p v-if="poll.text"><router-link to="`/@${ acct }/${ poll.id }`">{{ poll.text }}</router-link></p> | ||||
| 		<p v-if="!poll.text"><router-link to="`/@${ acct }/${ poll.id }`">%fa:link%</router-link></p> | ||||
| 		<mk-poll :post="poll"/> | ||||
| 		<mk-poll :note="poll"/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && poll == null">%i18n:desktop.tags.mk-recommended-polls-home-widget.nothing%</p> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
|  | @ -47,11 +47,11 @@ export default define({ | |||
| 			this.fetching = true; | ||||
| 			this.poll = null; | ||||
| 
 | ||||
| 			(this as any).api('posts/polls/recommendation', { | ||||
| 			(this as any).api('notes/polls/recommendation', { | ||||
| 				limit: 1, | ||||
| 				offset: this.offset | ||||
| 			}).then(posts => { | ||||
| 				const poll = posts ? posts[0] : null; | ||||
| 			}).then(notes => { | ||||
| 				const poll = notes ? notes[0] : null; | ||||
| 				if (poll == null) { | ||||
| 					this.offset = 0; | ||||
| 				} else { | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 		<p class="title">%fa:pencil-alt%%i18n:desktop.tags.mk-post-form-home-widget.title%</p> | ||||
| 	</template> | ||||
| 	<textarea :disabled="posting" v-model="text" @keydown="onKeydown" placeholder="%i18n:desktop.tags.mk-post-form-home-widget.placeholder%"></textarea> | ||||
| 	<button @click="post" :disabled="posting">%i18n:desktop.tags.mk-post-form-home-widget.post%</button> | ||||
| 	<button @click="post" :disabled="posting">%i18n:desktop.tags.mk-post-form-home-widget.note%</button> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -36,7 +36,7 @@ export default define({ | |||
| 		post() { | ||||
| 			this.posting = true; | ||||
| 
 | ||||
| 			(this as any).api('posts/create', { | ||||
| 			(this as any).api('notes/create', { | ||||
| 				text: this.text | ||||
| 			}).then(data => { | ||||
| 				this.clear(); | ||||
|  |  | |||
|  | @ -5,8 +5,8 @@ | |||
| 		<button @click="fetch" title="%i18n:desktop.tags.mk-trends-home-widget.refresh%">%fa:sync%</button> | ||||
| 	</template> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<div class="post" v-else-if="post != null"> | ||||
| 		<p class="text"><router-link :to="`/@${ acct }/${ post.id }`">{{ post.text }}</router-link></p> | ||||
| 	<div class="note" v-else-if="note != null"> | ||||
| 		<p class="text"><router-link :to="`/@${ acct }/${ note.id }`">{{ note.text }}</router-link></p> | ||||
| 		<p class="author">―<router-link :to="`/@${ acct }`">@{{ acct }}</router-link></p> | ||||
| 	</div> | ||||
| 	<p class="empty" v-else>%i18n:desktop.tags.mk-trends-home-widget.nothing%</p> | ||||
|  | @ -25,12 +25,12 @@ export default define({ | |||
| }).extend({ | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			post: null, | ||||
| 			note: null, | ||||
| 			fetching: true, | ||||
| 			offset: 0 | ||||
| 		}; | ||||
|  | @ -44,23 +44,23 @@ export default define({ | |||
| 		}, | ||||
| 		fetch() { | ||||
| 			this.fetching = true; | ||||
| 			this.post = null; | ||||
| 			this.note = null; | ||||
| 
 | ||||
| 			(this as any).api('posts/trend', { | ||||
| 			(this as any).api('notes/trend', { | ||||
| 				limit: 1, | ||||
| 				offset: this.offset, | ||||
| 				repost: false, | ||||
| 				renote: false, | ||||
| 				reply: false, | ||||
| 				media: false, | ||||
| 				poll: false | ||||
| 			}).then(posts => { | ||||
| 				const post = posts ? posts[0] : null; | ||||
| 				if (post == null) { | ||||
| 			}).then(notes => { | ||||
| 				const note = notes ? notes[0] : null; | ||||
| 				if (note == null) { | ||||
| 					this.offset = 0; | ||||
| 				} else { | ||||
| 					this.offset++; | ||||
| 				} | ||||
| 				this.post = post; | ||||
| 				this.note = note; | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		} | ||||
|  | @ -103,7 +103,7 @@ export default define({ | |||
| 		&:active | ||||
| 			color #999 | ||||
| 
 | ||||
| 	> .post | ||||
| 	> .note | ||||
| 		padding 16px | ||||
| 		font-size 12px | ||||
| 		font-style oblique | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
| 					<b-form-checkbox-group v-model="permission" stacked> | ||||
| 						<b-form-checkbox value="account-read">アカウントの情報を見る。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="account-write">アカウントの情報を操作する。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="post-write">投稿する。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="note-write">投稿する。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="reaction-write">リアクションしたりリアクションをキャンセルする。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="following-write">フォローしたりフォロー解除する。</b-form-checkbox> | ||||
| 						<b-form-checkbox value="drive-read">ドライブを見る。</b-form-checkbox> | ||||
|  |  | |||
|  | @ -1,24 +1,24 @@ | |||
| import PostForm from '../views/components/post-form.vue'; | ||||
| //import RepostForm from '../views/components/repost-form.vue';
 | ||||
| import getPostSummary from '../../../../renderers/get-post-summary'; | ||||
| //import RenoteForm from '../views/components/renote-form.vue';
 | ||||
| import getNoteSummary from '../../../../renderers/get-note-summary'; | ||||
| 
 | ||||
| export default (os) => (opts) => { | ||||
| 	const o = opts || {}; | ||||
| 
 | ||||
| 	if (o.repost) { | ||||
| 		/*const vm = new RepostForm({ | ||||
| 	if (o.renote) { | ||||
| 		/*const vm = new RenoteForm({ | ||||
| 			propsData: { | ||||
| 				repost: o.repost | ||||
| 				renote: o.renote | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 		vm.$once('cancel', recover); | ||||
| 		vm.$once('post', recover); | ||||
| 		vm.$once('note', recover); | ||||
| 		document.body.appendChild(vm.$el);*/ | ||||
| 
 | ||||
| 		const text = window.prompt(`「${getPostSummary(o.repost)}」をRepost`); | ||||
| 		const text = window.prompt(`「${getNoteSummary(o.renote)}」をRenote`); | ||||
| 		if (text == null) return; | ||||
| 		os.api('posts/create', { | ||||
| 			repostId: o.repost.id, | ||||
| 		os.api('notes/create', { | ||||
| 			renoteId: o.renote.id, | ||||
| 			text: text == '' ? undefined : text | ||||
| 		}); | ||||
| 	} else { | ||||
|  | @ -36,7 +36,7 @@ export default (os) => (opts) => { | |||
| 			} | ||||
| 		}).$mount(); | ||||
| 		vm.$once('cancel', recover); | ||||
| 		vm.$once('post', recover); | ||||
| 		vm.$once('note', recover); | ||||
| 		document.body.appendChild(vm.$el); | ||||
| 		(vm as any).focus(); | ||||
| 	} | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ import MkDrive from './views/pages/drive.vue'; | |||
| import MkNotifications from './views/pages/notifications.vue'; | ||||
| import MkMessaging from './views/pages/messaging.vue'; | ||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||
| import MkPost from './views/pages/post.vue'; | ||||
| import MkNote from './views/pages/note.vue'; | ||||
| import MkSearch from './views/pages/search.vue'; | ||||
| import MkFollowers from './views/pages/followers.vue'; | ||||
| import MkFollowing from './views/pages/following.vue'; | ||||
|  | @ -68,7 +68,7 @@ init((launch) => { | |||
| 			{ path: '/@:user', component: MkUser }, | ||||
| 			{ path: '/@:user/followers', component: MkFollowers }, | ||||
| 			{ path: '/@:user/following', component: MkFollowing }, | ||||
| 			{ path: '/@:user/:post', component: MkPost } | ||||
| 			{ path: '/@:user/:note', component: MkNote } | ||||
| 		] | ||||
| 	}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,14 +2,14 @@ | |||
| <div class="mk-activity"> | ||||
| 	<svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> | ||||
| 		<g v-for="(d, i) in data"> | ||||
| 			<rect width="0.8" :height="d.postsH" | ||||
| 				:x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH" | ||||
| 			<rect width="0.8" :height="d.notesH" | ||||
| 				:x="i + 0.1" :y="1 - d.notesH - d.repliesH - d.renotesH" | ||||
| 				fill="#41ddde"/> | ||||
| 			<rect width="0.8" :height="d.repliesH" | ||||
| 				:x="i + 0.1" :y="1 - d.repliesH - d.repostsH" | ||||
| 				:x="i + 0.1" :y="1 - d.repliesH - d.renotesH" | ||||
| 				fill="#f7796c"/> | ||||
| 			<rect width="0.8" :height="d.repostsH" | ||||
| 				:x="i + 0.1" :y="1 - d.repostsH" | ||||
| 			<rect width="0.8" :height="d.renotesH" | ||||
| 				:x="i + 0.1" :y="1 - d.renotesH" | ||||
| 				fill="#a1de41"/> | ||||
| 			</g> | ||||
| 	</svg> | ||||
|  | @ -32,12 +32,12 @@ export default Vue.extend({ | |||
| 			userId: this.user.id, | ||||
| 			limit: 30 | ||||
| 		}).then(data => { | ||||
| 			data.forEach(d => d.total = d.posts + d.replies + d.reposts); | ||||
| 			data.forEach(d => d.total = d.notes + d.replies + d.renotes); | ||||
| 			this.peak = Math.max.apply(null, data.map(d => d.total)); | ||||
| 			data.forEach(d => { | ||||
| 				d.postsH = d.posts / this.peak; | ||||
| 				d.notesH = d.notes / this.peak; | ||||
| 				d.repliesH = d.replies / this.peak; | ||||
| 				d.repostsH = d.reposts / this.peak; | ||||
| 				d.renotesH = d.renotes / this.peak; | ||||
| 			}); | ||||
| 			data.reverse(); | ||||
| 			this.data = data; | ||||
|  |  | |||
|  | @ -2,16 +2,16 @@ import Vue from 'vue'; | |||
| 
 | ||||
| import ui from './ui.vue'; | ||||
| import timeline from './timeline.vue'; | ||||
| import post from './post.vue'; | ||||
| import posts from './posts.vue'; | ||||
| import note from './note.vue'; | ||||
| import notes from './notes.vue'; | ||||
| import mediaImage from './media-image.vue'; | ||||
| import mediaVideo from './media-video.vue'; | ||||
| import drive from './drive.vue'; | ||||
| import postPreview from './post-preview.vue'; | ||||
| import subPostContent from './sub-post-content.vue'; | ||||
| import postCard from './post-card.vue'; | ||||
| import notePreview from './note-preview.vue'; | ||||
| import subNoteContent from './sub-note-content.vue'; | ||||
| import noteCard from './note-card.vue'; | ||||
| import userCard from './user-card.vue'; | ||||
| import postDetail from './post-detail.vue'; | ||||
| import noteDetail from './note-detail.vue'; | ||||
| import followButton from './follow-button.vue'; | ||||
| import friendsMaker from './friends-maker.vue'; | ||||
| import notification from './notification.vue'; | ||||
|  | @ -25,16 +25,16 @@ import widgetContainer from './widget-container.vue'; | |||
| 
 | ||||
| Vue.component('mk-ui', ui); | ||||
| Vue.component('mk-timeline', timeline); | ||||
| Vue.component('mk-post', post); | ||||
| Vue.component('mk-posts', posts); | ||||
| Vue.component('mk-note', note); | ||||
| Vue.component('mk-notes', notes); | ||||
| Vue.component('mk-media-image', mediaImage); | ||||
| Vue.component('mk-media-video', mediaVideo); | ||||
| Vue.component('mk-drive', drive); | ||||
| Vue.component('mk-post-preview', postPreview); | ||||
| Vue.component('mk-sub-post-content', subPostContent); | ||||
| Vue.component('mk-post-card', postCard); | ||||
| Vue.component('mk-note-preview', notePreview); | ||||
| Vue.component('mk-sub-note-content', subNoteContent); | ||||
| Vue.component('mk-note-card', noteCard); | ||||
| Vue.component('mk-user-card', userCard); | ||||
| Vue.component('mk-post-detail', postDetail); | ||||
| Vue.component('mk-note-detail', noteDetail); | ||||
| Vue.component('mk-follow-button', followButton); | ||||
| Vue.component('mk-friends-maker', friendsMaker); | ||||
| Vue.component('mk-notification', notification); | ||||
|  |  | |||
|  | @ -1,41 +1,41 @@ | |||
| <template> | ||||
| <div class="mk-post-card"> | ||||
| 	<a :href="`/@${acct}/${post.id}`"> | ||||
| <div class="mk-note-card"> | ||||
| 	<a :href="`/@${acct}/${note.id}`"> | ||||
| 		<header> | ||||
| 			<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3> | ||||
| 		</header> | ||||
| 		<div> | ||||
| 			{{ text }} | ||||
| 		</div> | ||||
| 		<mk-time :time="post.createdAt"/> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</a> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import summary from '../../../../../renderers/get-post-summary'; | ||||
| import summary from '../../../../../renderers/get-note-summary'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		text(): string { | ||||
| 			return summary(this.post); | ||||
| 			return summary(this.note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-post-card | ||||
| .mk-note-card | ||||
| 	display inline-block | ||||
| 	width 150px | ||||
| 	//height 120px | ||||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="root sub"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="time" :to="`/@${acct}/${post.id}`"> | ||||
| 				<mk-time :time="post.createdAt"/> | ||||
| 			<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-post-content class="text" :post="post"/> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										462
									
								
								src/client/app/mobile/views/components/note-detail.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								src/client/app/mobile/views/components/note-detail.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="mk-post-preview"> | ||||
| <div class="mk-note-preview"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="time" :to="`/@${acct}/${post.id}`"> | ||||
| 				<mk-time :time="post.createdAt"/> | ||||
| 			<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-post-content class="text" :post="post"/> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -24,20 +24,20 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-post-preview | ||||
| .mk-note-preview | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	font-size 0.9em | ||||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="sub"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="created-at" :to="`/@${acct}/${post.id}`"> | ||||
| 				<mk-time :time="post.createdAt"/> | ||||
| 			<router-link class="created-at" :to="`/@${acct}/${note.id}`"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-post-content class="text" :post="post"/> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | @ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										540
									
								
								src/client/app/mobile/views/components/note.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								src/client/app/mobile/views/components/note.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,12 +1,12 @@ | |||
| <template> | ||||
| <div class="mk-posts"> | ||||
| <div class="mk-notes"> | ||||
| 	<slot name="head"></slot> | ||||
| 	<slot></slot> | ||||
| 	<template v-for="(post, i) in _posts"> | ||||
| 		<mk-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/> | ||||
| 		<p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date"> | ||||
| 			<span>%fa:angle-up%{{ post._datetext }}</span> | ||||
| 			<span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span> | ||||
| 	<template v-for="(note, i) in _notes"> | ||||
| 		<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> | ||||
| 		<p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> | ||||
| 			<span>%fa:angle-up%{{ note._datetext }}</span> | ||||
| 			<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> | ||||
| 		</p> | ||||
| 	</template> | ||||
| 	<footer> | ||||
|  | @ -20,25 +20,25 @@ import Vue from 'vue'; | |||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		posts: { | ||||
| 		notes: { | ||||
| 			type: Array, | ||||
| 			default: () => [] | ||||
| 		} | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		_posts(): any[] { | ||||
| 			return (this.posts as any).map(post => { | ||||
| 				const date = new Date(post.createdAt).getDate(); | ||||
| 				const month = new Date(post.createdAt).getMonth() + 1; | ||||
| 				post._date = date; | ||||
| 				post._datetext = `${month}月 ${date}日`; | ||||
| 				return post; | ||||
| 		_notes(): any[] { | ||||
| 			return (this.notes as any).map(note => { | ||||
| 				const date = new Date(note.createdAt).getDate(); | ||||
| 				const month = new Date(note.createdAt).getMonth() + 1; | ||||
| 				note._date = date; | ||||
| 				note._datetext = `${month}月 ${date}日`; | ||||
| 				return note; | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onPostUpdated(i, post) { | ||||
| 			Vue.set((this as any).posts, i, post); | ||||
| 		onNoteUpdated(i, note) { | ||||
| 			Vue.set((this as any).notes, i, note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | @ -47,7 +47,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .mk-posts | ||||
| .mk-notes | ||||
| 	background #fff | ||||
| 	border-radius 8px | ||||
| 	box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) | ||||
|  | @ -4,23 +4,23 @@ | |||
| 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p> | ||||
| 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'repost'"> | ||||
| 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	<template v-if="notification.type == 'renote'"> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:retweet%{{ posterName }}</p> | ||||
| 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p> | ||||
| 			<p>%fa:retweet%{{ noteerName }}</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'quote'"> | ||||
| 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:quote-left%{{ posterName }}</p> | ||||
| 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p> | ||||
| 			<p>%fa:quote-left%{{ noteerName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 
 | ||||
|  | @ -32,18 +32,18 @@ | |||
| 	</template> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'reply'"> | ||||
| 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:reply%{{ posterName }}</p> | ||||
| 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p> | ||||
| 			<p>%fa:reply%{{ noteerName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'mention'"> | ||||
| 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:at%{{ posterName }}</p> | ||||
| 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p> | ||||
| 			<p>%fa:at%{{ noteerName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 
 | ||||
|  | @ -51,7 +51,7 @@ | |||
| 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:chart-pie%{{ name }}</p> | ||||
| 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| </div> | ||||
|  | @ -59,7 +59,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getPostSummary from '../../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|  | @ -68,13 +68,13 @@ export default Vue.extend({ | |||
| 		name() { | ||||
| 			return getUserName(this.notification.user); | ||||
| 		}, | ||||
| 		posterName() { | ||||
| 			return getUserName(this.notification.post.user); | ||||
| 		noteerName() { | ||||
| 			return getUserName(this.notification.note.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			getPostSummary | ||||
| 			getNoteSummary | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
|  | @ -112,7 +112,7 @@ export default Vue.extend({ | |||
| 			i, mk-reaction-icon | ||||
| 				margin-right 4px | ||||
| 
 | ||||
| 	.post-ref | ||||
| 	.note-ref | ||||
| 
 | ||||
| 		[data-fa] | ||||
| 			font-size 1em | ||||
|  | @ -121,7 +121,7 @@ export default Vue.extend({ | |||
| 			display inline-block | ||||
| 			margin-right 3px | ||||
| 
 | ||||
| 	&.repost, &.quote | ||||
| 	&.renote, &.quote | ||||
| 		.text p i | ||||
| 			color #77B255 | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,31 +10,31 @@ | |||
| 				<mk-reaction-icon :reaction="notification.reaction"/> | ||||
| 				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> | ||||
| 				%fa:quote-left%{{ getPostSummary(notification.post) }} | ||||
| 			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note) }} | ||||
| 				%fa:quote-right% | ||||
| 			</router-link> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="notification repost" v-if="notification.type == 'repost'"> | ||||
| 	<div class="notification renote" v-if="notification.type == 'renote'"> | ||||
| 		<mk-time :time="notification.createdAt"/> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 			<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 			<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="text"> | ||||
| 			<p> | ||||
| 				%fa:retweet% | ||||
| 				<router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link> | ||||
| 				<router-link :to="`/@${acct}`">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> | ||||
| 				%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% | ||||
| 			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% | ||||
| 			</router-link> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'quote'"> | ||||
| 		<mk-post :post="notification.post"/> | ||||
| 		<mk-note :note="notification.note"/> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<div class="notification follow" v-if="notification.type == 'follow'"> | ||||
|  | @ -51,11 +51,11 @@ | |||
| 	</div> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'reply'"> | ||||
| 		<mk-post :post="notification.post"/> | ||||
| 		<mk-note :note="notification.note"/> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<template v-if="notification.type == 'mention'"> | ||||
| 		<mk-post :post="notification.post"/> | ||||
| 		<mk-note :note="notification.note"/> | ||||
| 	</template> | ||||
| 
 | ||||
| 	<div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> | ||||
|  | @ -68,8 +68,8 @@ | |||
| 				%fa:chart-pie% | ||||
| 				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> | ||||
| 				%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% | ||||
| 			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| 			</router-link> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | @ -78,7 +78,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getPostSummary from '../../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| 
 | ||||
|  | @ -91,13 +91,13 @@ export default Vue.extend({ | |||
| 		name() { | ||||
| 			return getUserName(this.notification.user); | ||||
| 		}, | ||||
| 		posterName() { | ||||
|  			return getUserName(this.notification.post.user); | ||||
| 		noteerName() { | ||||
|  			return getUserName(this.notification.note.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			getPostSummary | ||||
| 			getNoteSummary | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
|  | @ -146,10 +146,10 @@ export default Vue.extend({ | |||
| 				i, .mk-reaction-icon | ||||
| 					margin-right 4px | ||||
| 
 | ||||
| 			> .post-preview | ||||
| 			> .note-preview | ||||
| 				color rgba(0, 0, 0, 0.7) | ||||
| 
 | ||||
| 			> .post-ref | ||||
| 			> .note-ref | ||||
| 				color rgba(0, 0, 0, 0.7) | ||||
| 
 | ||||
| 				[data-fa] | ||||
|  | @ -159,7 +159,7 @@ export default Vue.extend({ | |||
| 					display inline-block | ||||
| 					margin-right 3px | ||||
| 
 | ||||
| 		&.repost | ||||
| 		&.renote | ||||
| 			.text p i | ||||
| 				color #77B255 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <div class="mk-post-detail"> | ||||
| <div class="mk-note-detail"> | ||||
| 	<button | ||||
| 		class="more" | ||||
| 		v-if="p.reply && p.reply.replyId && context == null" | ||||
|  | @ -10,21 +10,21 @@ | |||
| 		<template v-if="contextFetching">%fa:spinner .pulse%</template> | ||||
| 	</button> | ||||
| 	<div class="context"> | ||||
| 		<x-sub v-for="post in context" :key="post.id" :post="post"/> | ||||
| 		<x-sub v-for="note in context" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :post="p.reply"/> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="repost" v-if="isRepost"> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<router-link class="name" :to="`/@${acct}`"> | ||||
| 				{{ name }} | ||||
| 			</router-link> | ||||
| 			がRepost | ||||
| 			がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
|  | @ -38,33 +38,33 @@ | |||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-post-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/> | ||||
| 			<mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/> | ||||
| 			<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 				<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 			</div> | ||||
| 			<div class="media" v-if="p.media.length > 0"> | ||||
| 				<mk-media-list :media-list="p.media"/> | ||||
| 			</div> | ||||
| 			<mk-poll v-if="p.poll" :post="p"/> | ||||
| 			<mk-poll v-if="p.poll" :note="p"/> | ||||
| 			<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| 			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 			<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 			<div class="repost" v-if="p.repost"> | ||||
| 				<mk-post-preview :post="p.repost"/> | ||||
| 			<div class="renote" v-if="p.renote"> | ||||
| 				<mk-note-preview :note="p.renote"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<router-link class="time" :to="`/@${pAcct}/${p.id}`"> | ||||
| 			<mk-time :time="p.createdAt" mode="detail"/> | ||||
| 		</router-link> | ||||
| 		<footer> | ||||
| 			<mk-reactions-viewer :post="p"/> | ||||
| 			<button @click="reply" title="%i18n:mobile.tags.mk-post-detail.reply%"> | ||||
| 			<mk-reactions-viewer :note="p"/> | ||||
| 			<button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%"> | ||||
| 				%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 			</button> | ||||
| 			<button @click="repost" title="Repost"> | ||||
| 				%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> | ||||
| 			<button @click="renote" title="Renote"> | ||||
| 				%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 			</button> | ||||
| 			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%"> | ||||
| 			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%"> | ||||
| 				%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
| 			</button> | ||||
| 			<button @click="menu" ref="menuButton"> | ||||
|  | @ -73,7 +73,7 @@ | |||
| 		</footer> | ||||
| 	</article> | ||||
| 	<div class="replies" v-if="!compact"> | ||||
| 		<x-sub v-for="post in replies" :key="post.id" :post="post"/> | ||||
| 		<x-sub v-for="note in replies" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -84,9 +84,9 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import parse from '../../../../../text/parse'; | ||||
| 
 | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './post-detail.sub.vue'; | ||||
| import XSub from './note-detail.sub.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
|  | @ -94,7 +94,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		post: { | ||||
| 		note: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
|  | @ -113,10 +113,10 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	computed: { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
|  | @ -124,14 +124,14 @@ export default Vue.extend({ | |||
| 		pName(): string { | ||||
| 			return getUserName(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
| 			return (this.post.repost && | ||||
| 				this.post.text == null && | ||||
| 				this.post.mediaIds == null && | ||||
| 				this.post.poll == null); | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds == null && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRepost ? this.post.repost : this.post; | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
|  | @ -155,8 +155,8 @@ export default Vue.extend({ | |||
| 	mounted() { | ||||
| 		// Get replies | ||||
| 		if (!this.compact) { | ||||
| 			(this as any).api('posts/replies', { | ||||
| 				postId: this.p.id, | ||||
| 			(this as any).api('notes/replies', { | ||||
| 				noteId: this.p.id, | ||||
| 				limit: 8 | ||||
| 			}).then(replies => { | ||||
| 				this.replies = replies; | ||||
|  | @ -187,8 +187,8 @@ export default Vue.extend({ | |||
| 			this.contextFetching = true; | ||||
| 
 | ||||
| 			// Fetch context | ||||
| 			(this as any).api('posts/context', { | ||||
| 				postId: this.p.replyId | ||||
| 			(this as any).api('notes/context', { | ||||
| 				noteId: this.p.replyId | ||||
| 			}).then(context => { | ||||
| 				this.contextFetching = false; | ||||
| 				this.context = context.reverse(); | ||||
|  | @ -199,22 +199,22 @@ export default Vue.extend({ | |||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		repost() { | ||||
| 		renote() { | ||||
| 			(this as any).apis.post({ | ||||
| 				repost: this.p | ||||
| 				renote: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				post: this.p, | ||||
| 				note: this.p, | ||||
| 				compact: true | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkPostMenu, { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				post: this.p, | ||||
| 				note: this.p, | ||||
| 				compact: true | ||||
| 			}); | ||||
| 		} | ||||
|  | @ -225,7 +225,7 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .mk-post-detail | ||||
| .mk-note-detail | ||||
| 	overflow hidden | ||||
| 	margin 0 auto | ||||
| 	padding 0 | ||||
|  | @ -267,7 +267,7 @@ export default Vue.extend({ | |||
| 		> * | ||||
| 			border-bottom 1px solid #eef0f2 | ||||
| 
 | ||||
| 	> .repost | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  | @ -357,10 +357,10 @@ export default Vue.extend({ | |||
| 		> .body | ||||
| 			padding 8px 0 | ||||
| 
 | ||||
| 			> .repost | ||||
| 			> .renote | ||||
| 				margin 8px 0 | ||||
| 
 | ||||
| 				> .mk-post-preview | ||||
| 				> .mk-note-preview | ||||
| 					padding 16px | ||||
| 					border dashed 1px #c0dac6 | ||||
| 					border-radius 8px | ||||
|  |  | |||
|  | @ -9,8 +9,8 @@ | |||
| 		</div> | ||||
| 	</header> | ||||
| 	<div class="form"> | ||||
| 		<mk-post-preview v-if="reply" :post="reply"/> | ||||
| 		<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea> | ||||
| 		<mk-note-preview v-if="reply" :note="reply"/> | ||||
| 		<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.note-placeholder%'"></textarea> | ||||
| 		<div class="attaches" v-show="files.length != 0"> | ||||
| 			<x-draggable class="files" :list="files" :options="{ animation: 150 }"> | ||||
| 				<div class="file" v-for="file in files" :key="file.id"> | ||||
|  | @ -112,7 +112,7 @@ export default Vue.extend({ | |||
| 		post() { | ||||
| 			this.posting = true; | ||||
| 			const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== true; | ||||
| 			(this as any).api('posts/create', { | ||||
| 			(this as any).api('notes/create', { | ||||
| 				text: this.text == '' ? undefined : this.text, | ||||
| 				mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, | ||||
| 				replyId: this.reply ? this.reply.id : undefined, | ||||
|  | @ -127,7 +127,7 @@ export default Vue.extend({ | |||
| 				} : null, | ||||
| 				viaMobile: viaMobile | ||||
| 			}).then(data => { | ||||
| 				this.$emit('post'); | ||||
| 				this.$emit('note'); | ||||
| 				this.$destroy(); | ||||
| 			}).catch(err => { | ||||
| 				this.posting = false; | ||||
|  | @ -200,7 +200,7 @@ export default Vue.extend({ | |||
| 		max-width 500px | ||||
| 		margin 0 auto | ||||
| 
 | ||||
| 		> .mk-post-preview | ||||
| 		> .mk-note-preview | ||||
| 			padding 16px | ||||
| 
 | ||||
| 		> .attaches | ||||
|  |  | |||
|  | @ -1,19 +1,19 @@ | |||
| <template> | ||||
| <div class="post" :class="{ repost: isRepost }"> | ||||
| <div class="note" :class="{ renote: isRenote }"> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :post="p.reply"/> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="repost" v-if="isRepost"> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> | ||||
| 			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 		</p> | ||||
| 		<mk-time :time="post.createdAt"/> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${pAcct}`"> | ||||
|  | @ -37,13 +37,13 @@ | |||
| 					<a class="reply" v-if="p.reply"> | ||||
| 						%fa:reply% | ||||
| 					</a> | ||||
| 					<mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.repost != null">RP:</a> | ||||
| 					<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.renote != null">RP:</a> | ||||
| 				</div> | ||||
| 				<div class="media" v-if="p.media.length > 0"> | ||||
| 					<mk-media-list :media-list="p.media"/> | ||||
| 				</div> | ||||
| 				<mk-poll v-if="p.poll" :post="p" ref="pollViewer"/> | ||||
| 				<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> | ||||
| 				<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 					<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 				</div> | ||||
|  | @ -51,17 +51,17 @@ | |||
| 				<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 				<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 				<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> | ||||
| 				<div class="repost" v-if="p.repost"> | ||||
| 					<mk-post-preview :post="p.repost"/> | ||||
| 				<div class="renote" v-if="p.renote"> | ||||
| 					<mk-note-preview :note="p.renote"/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<footer> | ||||
| 				<mk-reactions-viewer :post="p" ref="reactionsViewer"/> | ||||
| 				<mk-reactions-viewer :note="p" ref="reactionsViewer"/> | ||||
| 				<button @click="reply"> | ||||
| 					%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 				</button> | ||||
| 				<button @click="repost" title="Repost"> | ||||
| 					%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p> | ||||
| 				<button @click="renote" title="Renote"> | ||||
| 					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 				</button> | ||||
| 				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton"> | ||||
| 					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
|  | @ -81,16 +81,16 @@ import getAcct from '../../../../../acct/render'; | |||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import parse from '../../../../../text/parse'; | ||||
| 
 | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './post.sub.vue'; | ||||
| import XSub from './note.sub.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: ['post'], | ||||
| 	props: ['note'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
|  | @ -101,10 +101,10 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	computed: { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.post.user); | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.post.user); | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
|  | @ -112,14 +112,14 @@ export default Vue.extend({ | |||
| 		pName(): string { | ||||
| 			return getUserName(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
| 			return (this.post.repost && | ||||
| 				this.post.text == null && | ||||
| 				this.post.mediaIds == null && | ||||
| 				this.post.poll == null); | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds == null && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRepost ? this.post.repost : this.post; | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
|  | @ -192,7 +192,7 @@ export default Vue.extend({ | |||
| 					type: 'capture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated); | ||||
| 				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		decapture(withHandler = false) { | ||||
|  | @ -201,18 +201,18 @@ export default Vue.extend({ | |||
| 					type: 'decapture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated); | ||||
| 				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		onStreamConnected() { | ||||
| 			this.capture(); | ||||
| 		}, | ||||
| 		onStreamPostUpdated(data) { | ||||
| 			const post = data.post; | ||||
| 			if (post.id == this.post.id) { | ||||
| 				this.$emit('update:post', post); | ||||
| 			} else if (post.id == this.post.repostId) { | ||||
| 				this.post.repost = post; | ||||
| 		onStreamNoteUpdated(data) { | ||||
| 			const note = data.note; | ||||
| 			if (note.id == this.note.id) { | ||||
| 				this.$emit('update:note', note); | ||||
| 			} else if (note.id == this.note.renoteId) { | ||||
| 				this.note.renote = note; | ||||
| 			} | ||||
| 		}, | ||||
| 		reply() { | ||||
|  | @ -220,22 +220,22 @@ export default Vue.extend({ | |||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		repost() { | ||||
| 		renote() { | ||||
| 			(this as any).apis.post({ | ||||
| 				repost: this.p | ||||
| 				renote: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				post: this.p, | ||||
| 				note: this.p, | ||||
| 				compact: true | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkPostMenu, { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				post: this.p, | ||||
| 				note: this.p, | ||||
| 				compact: true | ||||
| 			}); | ||||
| 		} | ||||
|  | @ -246,14 +246,14 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| .post | ||||
| .note | ||||
| 	font-size 12px | ||||
| 	border-bottom solid 1px #eaeaea | ||||
| 
 | ||||
| 	&:first-child | ||||
| 		border-radius 8px 8px 0 0 | ||||
| 
 | ||||
| 		> .repost | ||||
| 		> .renote | ||||
| 			border-radius 8px 8px 0 0 | ||||
| 
 | ||||
| 	&:last-of-type | ||||
|  | @ -265,7 +265,7 @@ export default Vue.extend({ | |||
| 	@media (min-width 500px) | ||||
| 		font-size 16px | ||||
| 
 | ||||
| 	> .repost | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  | @ -309,7 +309,7 @@ export default Vue.extend({ | |||
| 	> .reply-to | ||||
| 		background rgba(0, 0, 0, 0.0125) | ||||
| 
 | ||||
| 		> .mk-post-preview | ||||
| 		> .mk-note-preview | ||||
| 			background transparent | ||||
| 
 | ||||
| 	> article | ||||
|  | @ -485,10 +485,10 @@ export default Vue.extend({ | |||
| 				> .mk-poll | ||||
| 					font-size 80% | ||||
| 
 | ||||
| 				> .repost | ||||
| 				> .renote | ||||
| 					margin 8px 0 | ||||
| 
 | ||||
| 					> .mk-post-preview | ||||
| 					> .mk-note-preview | ||||
| 						padding 16px | ||||
| 						border dashed 1px #c0dac6 | ||||
| 						border-radius 8px | ||||
|  |  | |||
							
								
								
									
										43
									
								
								src/client/app/mobile/views/components/sub-note-content.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/client/app/mobile/views/components/sub-note-content.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -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> | ||||
|  | @ -1,11 +1,11 @@ | |||
| <template> | ||||
| <div class="mk-timeline"> | ||||
| 	<mk-friends-maker v-if="alone"/> | ||||
| 	<mk-posts :posts="posts"> | ||||
| 	<mk-notes :notes="notes"> | ||||
| 		<div class="init" v-if="fetching"> | ||||
| 			%fa:spinner .pulse%%i18n:common.loading% | ||||
| 		</div> | ||||
| 		<div class="empty" v-if="!fetching && posts.length == 0"> | ||||
| 		<div class="empty" v-if="!fetching && notes.length == 0"> | ||||
| 			%fa:R comments% | ||||
| 			%i18n:mobile.tags.mk-home-timeline.empty-timeline% | ||||
| 		</div> | ||||
|  | @ -13,7 +13,7 @@ | |||
| 			<span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span> | ||||
| 			<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> | ||||
| 		</button> | ||||
| 	</mk-posts> | ||||
| 	</mk-notes> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -33,7 +33,7 @@ export default Vue.extend({ | |||
| 		return { | ||||
| 			fetching: true, | ||||
| 			moreFetching: false, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			existMore: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
|  | @ -48,14 +48,14 @@ export default Vue.extend({ | |||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('post', this.onPost); | ||||
| 		this.connection.on('note', this.onNote); | ||||
| 		this.connection.on('follow', this.onChangeFollowing); | ||||
| 		this.connection.on('unfollow', this.onChangeFollowing); | ||||
| 
 | ||||
| 		this.fetch(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('post', this.onPost); | ||||
| 		this.connection.off('note', this.onNote); | ||||
| 		this.connection.off('follow', this.onChangeFollowing); | ||||
| 		this.connection.off('unfollow', this.onChangeFollowing); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
|  | @ -63,15 +63,15 @@ export default Vue.extend({ | |||
| 	methods: { | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 			(this as any).api('posts/timeline', { | ||||
| 			(this as any).api('notes/timeline', { | ||||
| 				limit: limit + 1, | ||||
| 				untilDate: this.date ? (this.date as any).getTime() : undefined | ||||
| 			}).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				this.posts = posts; | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				this.$emit('loaded'); | ||||
| 				if (cb) cb(); | ||||
|  | @ -79,22 +79,22 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		more() { | ||||
| 			this.moreFetching = true; | ||||
| 			(this as any).api('posts/timeline', { | ||||
| 			(this as any).api('notes/timeline', { | ||||
| 				limit: limit + 1, | ||||
| 				untilId: this.posts[this.posts.length - 1].id | ||||
| 			}).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 				untilId: this.notes[this.notes.length - 1].id | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} else { | ||||
| 					this.existMore = false; | ||||
| 				} | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		}, | ||||
| 		onPost(post) { | ||||
| 			this.posts.unshift(post); | ||||
| 		onNote(note) { | ||||
| 			this.notes.unshift(note); | ||||
| 		}, | ||||
| 		onChangeFollowing() { | ||||
| 			this.fetch(); | ||||
|  |  | |||
|  | @ -1,18 +1,18 @@ | |||
| <template> | ||||
| <div class="mk-user-timeline"> | ||||
| 	<mk-posts :posts="posts"> | ||||
| 	<mk-notes :notes="notes"> | ||||
| 		<div class="init" v-if="fetching"> | ||||
| 			%fa:spinner .pulse%%i18n:common.loading% | ||||
| 		</div> | ||||
| 		<div class="empty" v-if="!fetching && posts.length == 0"> | ||||
| 		<div class="empty" v-if="!fetching && notes.length == 0"> | ||||
| 			%fa:R comments% | ||||
| 			{{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-posts-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-posts%' }} | ||||
| 			{{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-notes-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-notes%' }} | ||||
| 		</div> | ||||
| 		<button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail"> | ||||
| 			<span v-if="!moreFetching">%i18n:mobile.tags.mk-user-timeline.load-more%</span> | ||||
| 			<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> | ||||
| 		</button> | ||||
| 	</mk-posts> | ||||
| 	</mk-notes> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -26,22 +26,22 @@ export default Vue.extend({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			existMore: false, | ||||
| 			moreFetching: false | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/posts', { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withMedia: this.withMedia, | ||||
| 			limit: limit + 1 | ||||
| 		}).then(posts => { | ||||
| 			if (posts.length == limit + 1) { | ||||
| 				posts.pop(); | ||||
| 		}).then(notes => { | ||||
| 			if (notes.length == limit + 1) { | ||||
| 				notes.pop(); | ||||
| 				this.existMore = true; | ||||
| 			} | ||||
| 			this.posts = posts; | ||||
| 			this.notes = notes; | ||||
| 			this.fetching = false; | ||||
| 			this.$emit('loaded'); | ||||
| 		}); | ||||
|  | @ -49,19 +49,19 @@ export default Vue.extend({ | |||
| 	methods: { | ||||
| 		more() { | ||||
| 			this.moreFetching = true; | ||||
| 			(this as any).api('users/posts', { | ||||
| 			(this as any).api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				withMedia: this.withMedia, | ||||
| 				limit: limit + 1, | ||||
| 				untilId: this.posts[this.posts.length - 1].id | ||||
| 			}).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 				untilId: this.notes[this.notes.length - 1].id | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} else { | ||||
| 					this.existMore = false; | ||||
| 				} | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ import Vue from 'vue'; | |||
| import * as XDraggable from 'vuedraggable'; | ||||
| import * as uuid from 'uuid'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import getPostSummary from '../../../../../renderers/get-post-summary'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
|  | @ -124,14 +124,14 @@ export default Vue.extend({ | |||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('post', this.onStreamPost); | ||||
| 		this.connection.on('note', this.onStreamNote); | ||||
| 		this.connection.on('mobile_home_updated', this.onHomeUpdated); | ||||
| 		document.addEventListener('visibilitychange', this.onVisibilitychange, false); | ||||
| 
 | ||||
| 		Progress.start(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('post', this.onStreamPost); | ||||
| 		this.connection.off('note', this.onStreamNote); | ||||
| 		this.connection.off('mobile_home_updated', this.onHomeUpdated); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
| 		document.removeEventListener('visibilitychange', this.onVisibilitychange); | ||||
|  | @ -143,10 +143,10 @@ export default Vue.extend({ | |||
| 		onLoaded() { | ||||
| 			Progress.done(); | ||||
| 		}, | ||||
| 		onStreamPost(post) { | ||||
| 			if (document.hidden && post.userId !== (this as any).os.i.id) { | ||||
| 		onStreamNote(note) { | ||||
| 			if (document.hidden && note.userId !== (this as any).os.i.id) { | ||||
| 				this.unreadCount++; | ||||
| 				document.title = `(${this.unreadCount}) ${getPostSummary(post)}`; | ||||
| 				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`; | ||||
| 			} | ||||
| 		}, | ||||
| 		onVisibilitychange() { | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| <template> | ||||
| <mk-ui> | ||||
| 	<span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-post-page.title%</span> | ||||
| 	<span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-note-page.title%</span> | ||||
| 	<main v-if="!fetching"> | ||||
| 		<a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:mobile.tags.mk-post-page.next%</a> | ||||
| 		<a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:mobile.tags.mk-note-page.next%</a> | ||||
| 		<div> | ||||
| 			<mk-post-detail :post="post"/> | ||||
| 			<mk-note-detail :note="note"/> | ||||
| 		</div> | ||||
| 		<a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:mobile.tags.mk-post-page.prev%</a> | ||||
| 		<a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:mobile.tags.mk-note-page.prev%</a> | ||||
| 	</main> | ||||
| </mk-ui> | ||||
| </template> | ||||
|  | @ -19,7 +19,7 @@ export default Vue.extend({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			post: null | ||||
| 			note: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
|  | @ -37,10 +37,10 @@ export default Vue.extend({ | |||
| 			Progress.start(); | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('posts/show', { | ||||
| 				postId: this.$route.params.post | ||||
| 			}).then(post => { | ||||
| 				this.post = post; | ||||
| 			(this as any).api('notes/show', { | ||||
| 				noteId: this.$route.params.note | ||||
| 			}).then(note => { | ||||
| 				this.note = note; | ||||
| 				this.fetching = false; | ||||
| 
 | ||||
| 				Progress.done(); | ||||
|  | @ -2,13 +2,13 @@ | |||
| <mk-ui> | ||||
| 	<span slot="header">%fa:search% {{ q }}</span> | ||||
| 	<main v-if="!fetching"> | ||||
| 		<mk-posts :class="$style.posts" :posts="posts"> | ||||
| 			<span v-if="posts.length == 0">{{ '%i18n:mobile.tags.mk-search-posts.empty%'.replace('{}', q) }}</span> | ||||
| 		<mk-notes :class="$style.notes" :notes="notes"> | ||||
| 			<span v-if="notes.length == 0">{{ '%i18n:mobile.tags.mk-search-notes.empty%'.replace('{}', q) }}</span> | ||||
| 			<button v-if="existMore" @click="more" :disabled="fetching" slot="tail"> | ||||
| 				<span v-if="!fetching">%i18n:mobile.tags.mk-timeline.load-more%</span> | ||||
| 				<span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span> | ||||
| 			</button> | ||||
| 		</mk-posts> | ||||
| 		</mk-notes> | ||||
| 	</main> | ||||
| </mk-ui> | ||||
| </template> | ||||
|  | @ -25,7 +25,7 @@ export default Vue.extend({ | |||
| 		return { | ||||
| 			fetching: true, | ||||
| 			existMore: false, | ||||
| 			posts: [], | ||||
| 			notes: [], | ||||
| 			offset: 0 | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -48,30 +48,30 @@ export default Vue.extend({ | |||
| 			this.fetching = true; | ||||
| 			Progress.start(); | ||||
| 
 | ||||
| 			(this as any).api('posts/search', Object.assign({ | ||||
| 			(this as any).api('notes/search', Object.assign({ | ||||
| 				limit: limit + 1 | ||||
| 			}, parse(this.q))).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 			}, parse(this.q))).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				this.posts = posts; | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 				Progress.done(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			this.offset += limit; | ||||
| 			(this as any).api('posts/search', Object.assign({ | ||||
| 			(this as any).api('notes/search', Object.assign({ | ||||
| 				limit: limit + 1, | ||||
| 				offset: this.offset | ||||
| 			}, parse(this.q))).then(posts => { | ||||
| 				if (posts.length == limit + 1) { | ||||
| 					posts.pop(); | ||||
| 			}, parse(this.q))).then(notes => { | ||||
| 				if (notes.length == limit + 1) { | ||||
| 					notes.pop(); | ||||
| 				} else { | ||||
| 					this.existMore = false; | ||||
| 				} | ||||
| 				this.posts = this.posts.concat(posts); | ||||
| 				this.notes = this.notes.concat(notes); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -79,7 +79,7 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" module> | ||||
| .posts | ||||
| .notes | ||||
| 	margin 8px auto | ||||
| 	max-width 500px | ||||
| 	width calc(100% - 16px) | ||||
|  |  | |||
|  | @ -27,8 +27,8 @@ | |||
| 				</div> | ||||
| 				<div class="status"> | ||||
| 					<a> | ||||
| 						<b>{{ user.postsCount | number }}</b> | ||||
| 						<i>%i18n:mobile.tags.mk-user.posts%</i> | ||||
| 						<b>{{ user.notesCount | number }}</b> | ||||
| 						<i>%i18n:mobile.tags.mk-user.notes%</i> | ||||
| 					</a> | ||||
| 					<a :href="`@${acct}/following`"> | ||||
| 						<b>{{ user.followingCount | number }}</b> | ||||
|  | @ -44,13 +44,13 @@ | |||
| 		<nav> | ||||
| 			<div class="nav-container"> | ||||
| 				<a :data-is-active=" page == 'home' " @click="page = 'home'">%i18n:mobile.tags.mk-user.overview%</a> | ||||
| 				<a :data-is-active=" page == 'posts' " @click="page = 'posts'">%i18n:mobile.tags.mk-user.timeline%</a> | ||||
| 				<a :data-is-active=" page == 'notes' " @click="page = 'notes'">%i18n:mobile.tags.mk-user.timeline%</a> | ||||
| 				<a :data-is-active=" page == 'media' " @click="page = 'media'">%i18n:mobile.tags.mk-user.media%</a> | ||||
| 			</div> | ||||
| 		</nav> | ||||
| 		<div class="body"> | ||||
| 			<x-home v-if="page == 'home'" :user="user"/> | ||||
| 			<mk-user-timeline v-if="page == 'posts'" :user="user"/> | ||||
| 			<mk-user-timeline v-if="page == 'notes'" :user="user"/> | ||||
| 			<mk-user-timeline v-if="page == 'media'" :user="user" with-media/> | ||||
| 		</div> | ||||
| 	</main> | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| <template> | ||||
| <div class="root posts"> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching && posts.length > 0"> | ||||
| 		<mk-post-card v-for="post in posts" :key="post.id" :post="post"/> | ||||
| <div class="root notes"> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-notes.loading%<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching && notes.length > 0"> | ||||
| 		<mk-note-card v-for="note in notes" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && posts.length == 0">%i18n:mobile.tags.mk-user-overview-posts.no-posts%</p> | ||||
| 	<p class="empty" v-if="!fetching && notes.length == 0">%i18n:mobile.tags.mk-user-overview-notes.no-notes%</p> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -15,14 +15,14 @@ export default Vue.extend({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			posts: [] | ||||
| 			notes: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/posts', { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id | ||||
| 		}).then(posts => { | ||||
| 			this.posts = posts; | ||||
| 		}).then(notes => { | ||||
| 			this.notes = notes; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	} | ||||
|  | @ -30,7 +30,7 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .root.posts | ||||
| .root.notes | ||||
| 
 | ||||
| 	> div | ||||
| 		overflow-x scroll | ||||
|  | @ -5,7 +5,7 @@ | |||
| 		<a v-for="image in images" | ||||
| 			class="img" | ||||
| 			:style="`background-image: url(${image.media.url}?thumbnail&size=256)`" | ||||
| 			:href="`/@${getAcct(image.post.user)}/${image.post.id}`" | ||||
| 			:href="`/@${getAcct(image.note.user)}/${image.note.id}`" | ||||
| 		></a> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p> | ||||
|  | @ -28,15 +28,15 @@ export default Vue.extend({ | |||
| 		getAcct | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/posts', { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withMedia: true, | ||||
| 			limit: 6 | ||||
| 		}).then(posts => { | ||||
| 			posts.forEach(post => { | ||||
| 				post.media.forEach(media => { | ||||
| 		}).then(notes => { | ||||
| 			notes.forEach(note => { | ||||
| 				note.media.forEach(media => { | ||||
| 					if (this.images.length < 9) this.images.push({ | ||||
| 						post, | ||||
| 						note, | ||||
| 						media | ||||
| 					}); | ||||
| 				}); | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| <template> | ||||
| <div class="root home"> | ||||
| 	<mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/> | ||||
| 	<section class="recent-posts"> | ||||
| 		<h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> | ||||
| 	<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> | ||||
| 	<section class="recent-notes"> | ||||
| 		<h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-notes%</h2> | ||||
| 		<div> | ||||
| 			<x-posts :user="user"/> | ||||
| 			<x-notes :user="user"/> | ||||
| 		</div> | ||||
| 	</section> | ||||
| 	<section class="images"> | ||||
|  | @ -37,14 +37,14 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XPosts from './home.posts.vue'; | ||||
| import XNotes from './home.notes.vue'; | ||||
| import XPhotos from './home.photos.vue'; | ||||
| import XFriends from './home.friends.vue'; | ||||
| import XFollowersYouKnow from './home.followers-you-know.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XPosts, | ||||
| 		XNotes, | ||||
| 		XPhotos, | ||||
| 		XFriends, | ||||
| 		XFollowersYouKnow | ||||
|  | @ -58,7 +58,7 @@ export default Vue.extend({ | |||
| 	max-width 600px | ||||
| 	margin 0 auto | ||||
| 
 | ||||
| 	> .mk-post-detail | ||||
| 	> .mk-note-detail | ||||
| 		margin 0 0 8px 0 | ||||
| 
 | ||||
| 	> section | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 	<h1>Misskey<i>Statistics</i></h1> | ||||
| 	<main v-if="!initializing"> | ||||
| 		<mk-users stats={ stats }/> | ||||
| 		<mk-posts stats={ stats }/> | ||||
| 		<mk-notes stats={ stats }/> | ||||
| 	</main> | ||||
| 	<footer><a href={ _URL_ }>{ _HOST_ }</a></footer> | ||||
| 	<style lang="stylus" scoped> | ||||
|  | @ -56,9 +56,9 @@ | |||
| 	</script> | ||||
| </mk-index> | ||||
| 
 | ||||
| <mk-posts> | ||||
| 	<h2>%i18n:stats.posts-count% <b>{ stats.postsCount }</b></h2> | ||||
| 	<mk-posts-chart v-if="!initializing" data={ data }/> | ||||
| <mk-notes> | ||||
| 	<h2>%i18n:stats.notes-count% <b>{ stats.notesCount }</b></h2> | ||||
| 	<mk-notes-chart v-if="!initializing" data={ data }/> | ||||
| 	<style lang="stylus" scoped> | ||||
| 		:scope | ||||
| 			display block | ||||
|  | @ -70,7 +70,7 @@ | |||
| 		this.stats = this.opts.stats; | ||||
| 
 | ||||
| 		this.on('mount', () => { | ||||
| 			this.$root.$data.os.api('aggregation/posts', { | ||||
| 			this.$root.$data.os.api('aggregation/notes', { | ||||
| 				limit: 365 | ||||
| 			}).then(data => { | ||||
| 				this.update({ | ||||
|  | @ -80,7 +80,7 @@ | |||
| 			}); | ||||
| 		}); | ||||
| 	</script> | ||||
| </mk-posts> | ||||
| </mk-notes> | ||||
| 
 | ||||
| <mk-users> | ||||
| 	<h2>%i18n:stats.users-count% <b>{ stats.usersCount }</b></h2> | ||||
|  | @ -108,11 +108,11 @@ | |||
| 	</script> | ||||
| </mk-users> | ||||
| 
 | ||||
| <mk-posts-chart> | ||||
| <mk-notes-chart> | ||||
| 	<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> | ||||
| 		<title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title> | ||||
| 		<title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title> | ||||
| 		<polyline | ||||
| 			riot-points={ pointsPost } | ||||
| 			riot-points={ pointsNote } | ||||
| 			fill="none" | ||||
| 			stroke-width="1" | ||||
| 			stroke="#41ddde"/> | ||||
|  | @ -122,7 +122,7 @@ | |||
| 			stroke-width="1" | ||||
| 			stroke="#f7796c"/> | ||||
| 		<polyline | ||||
| 			riot-points={ pointsRepost } | ||||
| 			riot-points={ pointsRenote } | ||||
| 			fill="none" | ||||
| 			stroke-width="1" | ||||
| 			stroke="#a1de41"/> | ||||
|  | @ -147,7 +147,7 @@ | |||
| 		this.viewBoxY = 80; | ||||
| 
 | ||||
| 		this.data = this.opts.data.reverse(); | ||||
| 		this.data.forEach(d => d.total = d.posts + d.replies + d.reposts); | ||||
| 		this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); | ||||
| 		const peak = Math.max.apply(null, this.data.map(d => d.total)); | ||||
| 
 | ||||
| 		this.on('mount', () => { | ||||
|  | @ -156,14 +156,14 @@ | |||
| 
 | ||||
| 		this.render = () => { | ||||
| 			this.update({ | ||||
| 				pointsPost: this.data.map((d, i) => `${i},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '), | ||||
| 				pointsNote: this.data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '), | ||||
| 				pointsReply: this.data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '), | ||||
| 				pointsRepost: this.data.map((d, i) => `${i},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '), | ||||
| 				pointsRenote: this.data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '), | ||||
| 				pointsTotal: this.data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ') | ||||
| 			}); | ||||
| 		}; | ||||
| 	</script> | ||||
| </mk-posts-chart> | ||||
| </mk-notes-chart> | ||||
| 
 | ||||
| <mk-users-chart> | ||||
| 	<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none"> | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| endpoint: "posts/create" | ||||
| endpoint: "notes/create" | ||||
| 
 | ||||
| desc: | ||||
|   ja: "投稿します。" | ||||
|   en: "Compose new post." | ||||
|   en: "Compose new note." | ||||
| 
 | ||||
| params: | ||||
|   - name: "text" | ||||
|  | @ -10,7 +10,7 @@ params: | |||
|     optional: true | ||||
|     desc: | ||||
|       ja: "投稿の本文" | ||||
|       en: "The text of your post" | ||||
|       en: "The text of your note" | ||||
|   - name: "cw" | ||||
|     type: "string" | ||||
|     optional: true | ||||
|  | @ -24,17 +24,17 @@ params: | |||
|       ja: "添付するメディア(1~4つ)" | ||||
|       en: "Media you want to attach (1~4)" | ||||
|   - name: "replyId" | ||||
|     type: "id(Post)" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "返信する投稿" | ||||
|       en: "The post you want to reply" | ||||
|   - name: "repostId" | ||||
|     type: "id(Post)" | ||||
|       en: "The note you want to reply" | ||||
|   - name: "renoteId" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "引用する投稿" | ||||
|       en: "The post you want to quote" | ||||
|       en: "The note you want to quote" | ||||
|   - name: "poll" | ||||
|     type: "object" | ||||
|     optional: true | ||||
|  | @ -51,9 +51,9 @@ params: | |||
|           en: "Choices of a poll" | ||||
| 
 | ||||
| res: | ||||
|   - name: "createdPost" | ||||
|     type: "entity(Post)" | ||||
|   - name: "createdNote" | ||||
|     type: "entity(Note)" | ||||
|     optional: false | ||||
|     desc: | ||||
|       ja: "作成した投稿" | ||||
|       en: "A post that created" | ||||
|       en: "A note that created" | ||||
|  | @ -1,4 +1,4 @@ | |||
| endpoint: "posts/timeline" | ||||
| endpoint: "notes/timeline" | ||||
| 
 | ||||
| desc: | ||||
|   ja: "タイムラインを取得します。" | ||||
|  | @ -11,12 +11,12 @@ params: | |||
|     desc: | ||||
|       ja: "取得する最大の数" | ||||
|   - name: "sinceId" | ||||
|     type: "id(Post)" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "指定すると、この投稿を基点としてより新しい投稿を取得します" | ||||
|   - name: "untilId" | ||||
|     type: "id(Post)" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "指定すると、この投稿を基点としてより古い投稿を取得します" | ||||
							
								
								
									
										174
									
								
								src/client/docs/api/entities/note.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/client/docs/api/entities/note.yaml
									
										
									
									
									
										Normal 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: "速度。メートル / 秒数で表す。" | ||||
|  | @ -1,8 +1,8 @@ | |||
| name: "Post" | ||||
| name: "Note" | ||||
| 
 | ||||
| desc: | ||||
|   ja: "投稿。" | ||||
|   en: "A post." | ||||
|   en: "A note." | ||||
| 
 | ||||
| props: | ||||
|   - name: "id" | ||||
|  | @ -10,31 +10,31 @@ props: | |||
|     optional: false | ||||
|     desc: | ||||
|       ja: "投稿ID" | ||||
|       en: "The ID of this post" | ||||
|       en: "The ID of this note" | ||||
|   - name: "createdAt" | ||||
|     type: "date" | ||||
|     optional: false | ||||
|     desc: | ||||
|       ja: "投稿日時" | ||||
|       en: "The posted date of this post" | ||||
|       en: "The posted date of this note" | ||||
|   - name: "viaMobile" | ||||
|     type: "boolean" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "モバイル端末から投稿したか否か(自己申告であることに留意)" | ||||
|       en: "Whether this post sent via a mobile device" | ||||
|       en: "Whether this note sent via a mobile device" | ||||
|   - name: "text" | ||||
|     type: "string" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" | ||||
|       en: "The text of this post (in Markdown like format if local)" | ||||
|       en: "The text of this note (in Markdown like format if local)" | ||||
|   - name: "textHtml" | ||||
|     type: "string" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "投稿の本文 (HTML) (投稿時は無視)" | ||||
|       en: "The text of this post (in HTML. Ignored when posting.)" | ||||
|       en: "The text of this note (in HTML. Ignored when posting.)" | ||||
|   - name: "mediaIds" | ||||
|     type: "id(DriveFile)[]" | ||||
|     optional: true | ||||
|  | @ -52,48 +52,48 @@ props: | |||
|     optional: false | ||||
|     desc: | ||||
|       ja: "投稿者ID" | ||||
|       en: "The ID of author of this post" | ||||
|       en: "The ID of author of this note" | ||||
|   - name: "user" | ||||
|     type: "entity(User)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "投稿者" | ||||
|       en: "The author of this post" | ||||
|       en: "The author of this note" | ||||
|   - name: "myReaction" | ||||
|     type: "string" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>" | ||||
|       en: "The your <a href='/docs/api/reactions'>reaction</a> of this post" | ||||
|       en: "The your <a href='/docs/api/reactions'>reaction</a> of this note" | ||||
|   - name: "reactionCounts" | ||||
|     type: "object" | ||||
|     optional: false | ||||
|     desc: | ||||
|       ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト" | ||||
|   - name: "replyId" | ||||
|     type: "id(Post)" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "返信した投稿のID" | ||||
|       en: "The ID of the replyed post" | ||||
|       en: "The ID of the replyed note" | ||||
|   - name: "reply" | ||||
|     type: "entity(Post)" | ||||
|     type: "entity(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "返信した投稿" | ||||
|       en: "The replyed post" | ||||
|   - name: "repostId" | ||||
|     type: "id(Post)" | ||||
|       en: "The replyed note" | ||||
|   - name: "renoteId" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "引用した投稿のID" | ||||
|       en: "The ID of the quoted post" | ||||
|   - name: "repost" | ||||
|     type: "entity(Post)" | ||||
|       en: "The ID of the quoted note" | ||||
|   - name: "renote" | ||||
|     type: "entity(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "引用した投稿" | ||||
|       en: "The quoted post" | ||||
|       en: "The quoted note" | ||||
|   - name: "poll" | ||||
|     type: "object" | ||||
|     optional: true | ||||
|  |  | |||
|  | @ -81,24 +81,24 @@ props: | |||
|     desc: | ||||
|       ja: "自分がこのユーザーをミュートしているか" | ||||
|       en: "Whether you muted this user" | ||||
|   - name: "postsCount" | ||||
|   - name: "notesCount" | ||||
|     type: "number" | ||||
|     optional: false | ||||
|     desc: | ||||
|       ja: "投稿の数" | ||||
|       en: "The number of the posts of this user" | ||||
|   - name: "pinnedPost" | ||||
|     type: "entity(Post)" | ||||
|       en: "The number of the notes of this user" | ||||
|   - name: "pinnedNote" | ||||
|     type: "entity(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "ピン留めされた投稿" | ||||
|       en: "The pinned post of this user" | ||||
|   - name: "pinnedPostId" | ||||
|     type: "id(Post)" | ||||
|       en: "The pinned note of this user" | ||||
|   - name: "pinnedNoteId" | ||||
|     type: "id(Note)" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja: "ピン留めされた投稿のID" | ||||
|       en: "The ID of the pinned post of this user" | ||||
|       en: "The ID of the pinned note of this user" | ||||
|   - name: "driveCapacity" | ||||
|     type: "number" | ||||
|     optional: false | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ p ユーザーページから、そのユーザーをミュートすることが | |||
| 
 | ||||
| p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります: | ||||
| ul | ||||
| 	li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRepost) | ||||
| 	li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote) | ||||
| 	li そのユーザーからの通知 | ||||
| 	li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴 | ||||
| 
 | ||||
|  |  | |||
|  | @ -64,19 +64,19 @@ section | |||
| 			tr | ||||
| 				td mute | ||||
| 				td | ||||
| 					| mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostを除外する(デフォルト) | ||||
| 					| mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteを除外する(デフォルト) | ||||
| 					br | ||||
| 					| mute_related ... ミュートしているユーザーの投稿に対する返信やRepostだけ除外する | ||||
| 					| mute_related ... ミュートしているユーザーの投稿に対する返信やRenoteだけ除外する | ||||
| 					br | ||||
| 					| mute_direct ... ミュートしているユーザーの投稿だけ除外する | ||||
| 					br | ||||
| 					| disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostも含める | ||||
| 					| disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteも含める | ||||
| 					br | ||||
| 					| direct_only ... ミュートしているユーザーの投稿だけに限定 | ||||
| 					br | ||||
| 					| related_only ... ミュートしているユーザーの投稿に対する返信やRepostだけに限定 | ||||
| 					| related_only ... ミュートしているユーザーの投稿に対する返信やRenoteだけに限定 | ||||
| 					br | ||||
| 					| all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostに限定 | ||||
| 					| all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRenoteに限定 | ||||
| 			tr | ||||
| 				td reply | ||||
| 				td | ||||
|  | @ -86,11 +86,11 @@ section | |||
| 					br | ||||
| 					| null ... 特に限定しない(デフォルト) | ||||
| 			tr | ||||
| 				td repost | ||||
| 				td renote | ||||
| 				td | ||||
| 					| true ... Repostに限定。 | ||||
| 					| true ... Renoteに限定。 | ||||
| 					br | ||||
| 					| false ... Repostでない投稿に限定。 | ||||
| 					| false ... Renoteでない投稿に限定。 | ||||
| 					br | ||||
| 					| null ... 特に限定しない(デフォルト) | ||||
| 			tr | ||||
|  |  | |||
|  | @ -8,5 +8,5 @@ export type IFavorite = { | |||
| 	_id: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	userId: mongo.ObjectID; | ||||
| 	postId: mongo.ObjectID; | ||||
| 	noteId: mongo.ObjectID; | ||||
| }; | ||||
|  |  | |||
|  | @ -1,17 +1,17 @@ | |||
| import * as mongo from 'mongodb'; | ||||
| import deepcopy = require('deepcopy'); | ||||
| import db from '../db/mongodb'; | ||||
| import Reaction from './post-reaction'; | ||||
| import Reaction from './note-reaction'; | ||||
| import { pack as packUser } from './user'; | ||||
| 
 | ||||
| const PostReaction = db.get<IPostReaction>('postReactions'); | ||||
| PostReaction.createIndex(['userId', 'postId'], { unique: true }); | ||||
| export default PostReaction; | ||||
| const NoteReaction = db.get<INoteReaction>('noteReactions'); | ||||
| NoteReaction.createIndex(['userId', 'noteId'], { unique: true }); | ||||
| export default NoteReaction; | ||||
| 
 | ||||
| export interface IPostReaction { | ||||
| export interface INoteReaction { | ||||
| 	_id: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	postId: mongo.ObjectID; | ||||
| 	noteId: mongo.ObjectID; | ||||
| 	userId: mongo.ObjectID; | ||||
| 	reaction: string; | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/models/note-watching.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/models/note-watching.ts
									
										
									
									
									
										Normal 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; | ||||
| } | ||||
|  | @ -6,14 +6,14 @@ import { IUser, pack as packUser } from './user'; | |||
| import { pack as packApp } from './app'; | ||||
| import { pack as packChannel } from './channel'; | ||||
| import Vote from './poll-vote'; | ||||
| import Reaction from './post-reaction'; | ||||
| import Reaction from './note-reaction'; | ||||
| 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 { | ||||
| 	return text.length <= 1000 && text.trim() != ''; | ||||
|  | @ -23,14 +23,14 @@ export function isValidCw(text: string): boolean { | |||
| 	return text.length <= 100 && text.trim() != ''; | ||||
| } | ||||
| 
 | ||||
| export type IPost = { | ||||
| export type INote = { | ||||
| 	_id: mongo.ObjectID; | ||||
| 	channelId: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	deletedAt: Date; | ||||
| 	mediaIds: mongo.ObjectID[]; | ||||
| 	replyId: mongo.ObjectID; | ||||
| 	repostId: mongo.ObjectID; | ||||
| 	renoteId: mongo.ObjectID; | ||||
| 	poll: any; // todo
 | ||||
| 	text: string; | ||||
| 	tags: string[]; | ||||
|  | @ -39,7 +39,7 @@ export type IPost = { | |||
| 	userId: mongo.ObjectID; | ||||
| 	appId: mongo.ObjectID; | ||||
| 	viaMobile: boolean; | ||||
| 	repostCount: number; | ||||
| 	renoteCount: number; | ||||
| 	repliesCount: number; | ||||
| 	reactionCounts: any; | ||||
| 	mentions: mongo.ObjectID[]; | ||||
|  | @ -57,7 +57,7 @@ export type IPost = { | |||
| 	_reply?: { | ||||
| 		userId: mongo.ObjectID; | ||||
| 	}; | ||||
| 	_repost?: { | ||||
| 	_renote?: { | ||||
| 		userId: mongo.ObjectID; | ||||
| 	}; | ||||
| 	_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 options? serialize options | ||||
|  * @return response | ||||
|  */ | ||||
| export const pack = async ( | ||||
| 	post: string | mongo.ObjectID | IPost, | ||||
| 	note: string | mongo.ObjectID | INote, | ||||
| 	me?: string | mongo.ObjectID | IUser, | ||||
| 	options?: { | ||||
| 		detail: boolean | ||||
|  | @ -97,58 +97,58 @@ export const pack = async ( | |||
| 				: (me as IUser)._id | ||||
| 		: null; | ||||
| 
 | ||||
| 	let _post: any; | ||||
| 	let _note: any; | ||||
| 
 | ||||
| 	// Populate the post if 'post' is ID
 | ||||
| 	if (mongo.ObjectID.prototype.isPrototypeOf(post)) { | ||||
| 		_post = await Post.findOne({ | ||||
| 			_id: post | ||||
| 	// Populate the note if 'note' is ID
 | ||||
| 	if (mongo.ObjectID.prototype.isPrototypeOf(note)) { | ||||
| 		_note = await Note.findOne({ | ||||
| 			_id: note | ||||
| 		}); | ||||
| 	} else if (typeof post === 'string') { | ||||
| 		_post = await Post.findOne({ | ||||
| 			_id: new mongo.ObjectID(post) | ||||
| 	} else if (typeof note === 'string') { | ||||
| 		_note = await Note.findOne({ | ||||
| 			_id: new mongo.ObjectID(note) | ||||
| 		}); | ||||
| 	} 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
 | ||||
| 	_post.id = _post._id; | ||||
| 	delete _post._id; | ||||
| 	_note.id = _note._id; | ||||
| 	delete _note._id; | ||||
| 
 | ||||
| 	delete _post.mentions; | ||||
| 	if (_post.geo) delete _post.geo.type; | ||||
| 	delete _note.mentions; | ||||
| 	if (_note.geo) delete _note.geo.type; | ||||
| 
 | ||||
| 	// Populate user
 | ||||
| 	_post.user = packUser(_post.userId, meId); | ||||
| 	_note.user = packUser(_note.userId, meId); | ||||
| 
 | ||||
| 	// Populate app
 | ||||
| 	if (_post.appId) { | ||||
| 		_post.app = packApp(_post.appId); | ||||
| 	if (_note.appId) { | ||||
| 		_note.app = packApp(_note.appId); | ||||
| 	} | ||||
| 
 | ||||
| 	// Populate channel
 | ||||
| 	if (_post.channelId) { | ||||
| 		_post.channel = packChannel(_post.channelId); | ||||
| 	if (_note.channelId) { | ||||
| 		_note.channel = packChannel(_note.channelId); | ||||
| 	} | ||||
| 
 | ||||
| 	// Populate media
 | ||||
| 	if (_post.mediaIds) { | ||||
| 		_post.media = Promise.all(_post.mediaIds.map(fileId => | ||||
| 	if (_note.mediaIds) { | ||||
| 		_note.media = Promise.all(_note.mediaIds.map(fileId => | ||||
| 			packFile(fileId) | ||||
| 		)); | ||||
| 	} | ||||
| 
 | ||||
| 	// When requested a detailed post data
 | ||||
| 	// When requested a detailed note data
 | ||||
| 	if (opts.detail) { | ||||
| 		// Get previous post info
 | ||||
| 		_post.prev = (async () => { | ||||
| 			const prev = await Post.findOne({ | ||||
| 				userId: _post.userId, | ||||
| 		// Get previous note info
 | ||||
| 		_note.prev = (async () => { | ||||
| 			const prev = await Note.findOne({ | ||||
| 				userId: _note.userId, | ||||
| 				_id: { | ||||
| 					$lt: id | ||||
| 				} | ||||
|  | @ -163,10 +163,10 @@ export const pack = async ( | |||
| 			return prev ? prev._id : null; | ||||
| 		})(); | ||||
| 
 | ||||
| 		// Get next post info
 | ||||
| 		_post.next = (async () => { | ||||
| 			const next = await Post.findOne({ | ||||
| 				userId: _post.userId, | ||||
| 		// Get next note info
 | ||||
| 		_note.next = (async () => { | ||||
| 			const next = await Note.findOne({ | ||||
| 				userId: _note.userId, | ||||
| 				_id: { | ||||
| 					$gt: id | ||||
| 				} | ||||
|  | @ -181,27 +181,27 @@ export const pack = async ( | |||
| 			return next ? next._id : null; | ||||
| 		})(); | ||||
| 
 | ||||
| 		if (_post.replyId) { | ||||
| 			// Populate reply to post
 | ||||
| 			_post.reply = pack(_post.replyId, meId, { | ||||
| 		if (_note.replyId) { | ||||
| 			// Populate reply to note
 | ||||
| 			_note.reply = pack(_note.replyId, meId, { | ||||
| 				detail: false | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		if (_post.repostId) { | ||||
| 			// Populate repost
 | ||||
| 			_post.repost = pack(_post.repostId, meId, { | ||||
| 				detail: _post.text == null | ||||
| 		if (_note.renoteId) { | ||||
| 			// Populate renote
 | ||||
| 			_note.renote = pack(_note.renoteId, meId, { | ||||
| 				detail: _note.text == null | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// Poll
 | ||||
| 		if (meId && _post.poll) { | ||||
| 			_post.poll = (async (poll) => { | ||||
| 		if (meId && _note.poll) { | ||||
| 			_note.poll = (async (poll) => { | ||||
| 				const vote = await Vote | ||||
| 					.findOne({ | ||||
| 						userId: meId, | ||||
| 						postId: id | ||||
| 						noteId: id | ||||
| 					}); | ||||
| 
 | ||||
| 				if (vote != null) { | ||||
|  | @ -212,16 +212,16 @@ export const pack = async ( | |||
| 				} | ||||
| 
 | ||||
| 				return poll; | ||||
| 			})(_post.poll); | ||||
| 			})(_note.poll); | ||||
| 		} | ||||
| 
 | ||||
| 		// Fetch my reaction
 | ||||
| 		if (meId) { | ||||
| 			_post.myReaction = (async () => { | ||||
| 			_note.myReaction = (async () => { | ||||
| 				const reaction = await Reaction | ||||
| 					.findOne({ | ||||
| 						userId: meId, | ||||
| 						postId: id, | ||||
| 						noteId: id, | ||||
| 						deletedAt: { $exists: false } | ||||
| 					}); | ||||
| 
 | ||||
|  | @ -234,8 +234,8 @@ export const pack = async ( | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// resolve promises in _post object
 | ||||
| 	_post = await rap(_post); | ||||
| 	// resolve promises in _note object
 | ||||
| 	_note = await rap(_note); | ||||
| 
 | ||||
| 	return _post; | ||||
| 	return _note; | ||||
| }; | ||||
|  | @ -2,7 +2,7 @@ import * as mongo from 'mongodb'; | |||
| import deepcopy = require('deepcopy'); | ||||
| import db from '../db/mongodb'; | ||||
| 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'); | ||||
| export default Notification; | ||||
|  | @ -36,12 +36,12 @@ export interface INotification { | |||
| 	 * follow - フォローされた | ||||
| 	 * mention - 投稿で自分が言及された | ||||
| 	 * reply - (自分または自分がWatchしている)投稿が返信された | ||||
| 	 * repost - (自分または自分がWatchしている)投稿がRepostされた | ||||
| 	 * quote - (自分または自分がWatchしている)投稿が引用Repostされた | ||||
| 	 * renote - (自分または自分がWatchしている)投稿がRenoteされた | ||||
| 	 * quote - (自分または自分がWatchしている)投稿が引用Renoteされた | ||||
| 	 * reaction - (自分または自分が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; | ||||
| 		case 'mention': | ||||
| 		case 'reply': | ||||
| 		case 'repost': | ||||
| 		case 'renote': | ||||
| 		case 'quote': | ||||
| 		case 'reaction': | ||||
| 		case 'poll_vote': | ||||
| 			// Populate post
 | ||||
| 			_notification.post = await packPost(_notification.postId, me); | ||||
| 			// Populate note
 | ||||
| 			_notification.note = await packNote(_notification.noteId, me); | ||||
| 			break; | ||||
| 		default: | ||||
| 			console.error(`Unknown type: ${_notification.type}`); | ||||
|  |  | |||
|  | @ -8,6 +8,6 @@ export interface IPollVote { | |||
| 	_id: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	userId: mongo.ObjectID; | ||||
| 	postId: mongo.ObjectID; | ||||
| 	noteId: mongo.ObjectID; | ||||
| 	choice: number; | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ import * as mongo from 'mongodb'; | |||
| import deepcopy = require('deepcopy'); | ||||
| import rap from '@prezzemolo/rap'; | ||||
| import db from '../db/mongodb'; | ||||
| import { IPost, pack as packPost } from './post'; | ||||
| import { INote, pack as packNote } from './note'; | ||||
| import Following from './following'; | ||||
| import Mute from './mute'; | ||||
| import getFriends from '../server/api/common/get-friends'; | ||||
|  | @ -22,7 +22,7 @@ type IUserBase = { | |||
| 	followersCount: number; | ||||
| 	followingCount: number; | ||||
| 	name?: string; | ||||
| 	postsCount: number; | ||||
| 	notesCount: number; | ||||
| 	driveCapacity: number; | ||||
| 	username: string; | ||||
| 	usernameLower: string; | ||||
|  | @ -30,8 +30,8 @@ type IUserBase = { | |||
| 	bannerId: mongo.ObjectID; | ||||
| 	data: any; | ||||
| 	description: string; | ||||
| 	latestPost: IPost; | ||||
| 	pinnedPostId: mongo.ObjectID; | ||||
| 	latestNote: INote; | ||||
| 	pinnedNoteId: mongo.ObjectID; | ||||
| 	isSuspended: boolean; | ||||
| 	keywords: string[]; | ||||
| 	host: string; | ||||
|  | @ -120,7 +120,7 @@ export function init(user): IUser { | |||
| 	user._id = new mongo.ObjectID(user._id); | ||||
| 	user.avatarId = new mongo.ObjectID(user.avatarId); | ||||
| 	user.bannerId = new mongo.ObjectID(user.bannerId); | ||||
| 	user.pinnedPostId = new mongo.ObjectID(user.pinnedPostId); | ||||
| 	user.pinnedNoteId = new mongo.ObjectID(user.pinnedNoteId); | ||||
| 	return user; | ||||
| } | ||||
| 
 | ||||
|  | @ -186,7 +186,7 @@ export const pack = ( | |||
| 	delete _user._id; | ||||
| 
 | ||||
| 	// Remove needless properties
 | ||||
| 	delete _user.latestPost; | ||||
| 	delete _user.latestNote; | ||||
| 
 | ||||
| 	if (!_user.host) { | ||||
| 		// Remove private properties
 | ||||
|  | @ -260,9 +260,9 @@ export const pack = ( | |||
| 	} | ||||
| 
 | ||||
| 	if (opts.detail) { | ||||
| 		if (_user.pinnedPostId) { | ||||
| 			// Populate pinned post
 | ||||
| 			_user.pinnedPost = packPost(_user.pinnedPostId, meId, { | ||||
| 		if (_user.pinnedNoteId) { | ||||
| 			// Populate pinned note
 | ||||
| 			_user.pinnedNote = packNote(_user.pinnedNoteId, meId, { | ||||
| 				detail: true | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ const id = conf.othello_ai.id; | |||
|  */ | ||||
| const i = conf.othello_ai.i; | ||||
| 
 | ||||
| let post; | ||||
| let note; | ||||
| 
 | ||||
| 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})さんと始めました! (強さ${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, | ||||
| 				text: `${text}\n→[観戦する](${url})` | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		post = res.createdPost; | ||||
| 		note = res.createdNote; | ||||
| 		//#endregion
 | ||||
| 	} | ||||
| 
 | ||||
|  | @ -83,9 +83,9 @@ process.on('message', async msg => { | |||
| 					? `?[${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, | ||||
| 				repostId: post.id, | ||||
| 				renoteId: note.id, | ||||
| 				text: text | ||||
| 			} | ||||
| 		}); | ||||
|  |  | |||
|  | @ -46,28 +46,28 @@ homeStream.on('message', message => { | |||
| 
 | ||||
| 	// タイムライン上でなんか言われたまたは返信されたとき
 | ||||
| 	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, | ||||
| 				postId: post.id, | ||||
| 				noteId: note.id, | ||||
| 				reaction: 'love' | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		if (post.text) { | ||||
| 			if (post.text.indexOf('オセロ') > -1) { | ||||
| 				request.post(`${conf.api_url}/posts/create`, { | ||||
| 		if (note.text) { | ||||
| 			if (note.text.indexOf('オセロ') > -1) { | ||||
| 				request.post(`${conf.api_url}/notes/create`, { | ||||
| 					json: { i, | ||||
| 						replyId: post.id, | ||||
| 						replyId: note.id, | ||||
| 						text: '良いですよ~' | ||||
| 					} | ||||
| 				}); | ||||
| 
 | ||||
| 				invite(post.userId); | ||||
| 				invite(note.userId); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue