@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user