@@ -2,14 +2,14 @@
 | 
			
		||||
<div class="mk-activity">
 | 
			
		||||
	<svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none">
 | 
			
		||||
		<g v-for="(d, i) in data">
 | 
			
		||||
			<rect width="0.8" :height="d.postsH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH"
 | 
			
		||||
			<rect width="0.8" :height="d.notesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.notesH - d.repliesH - d.renotesH"
 | 
			
		||||
				fill="#41ddde"/>
 | 
			
		||||
			<rect width="0.8" :height="d.repliesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.repliesH - d.repostsH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.repliesH - d.renotesH"
 | 
			
		||||
				fill="#f7796c"/>
 | 
			
		||||
			<rect width="0.8" :height="d.repostsH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.repostsH"
 | 
			
		||||
			<rect width="0.8" :height="d.renotesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.renotesH"
 | 
			
		||||
				fill="#a1de41"/>
 | 
			
		||||
			</g>
 | 
			
		||||
	</svg>
 | 
			
		||||
@@ -32,12 +32,12 @@ export default Vue.extend({
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			limit: 30
 | 
			
		||||
		}).then(data => {
 | 
			
		||||
			data.forEach(d => d.total = d.posts + d.replies + d.reposts);
 | 
			
		||||
			data.forEach(d => d.total = d.notes + d.replies + d.renotes);
 | 
			
		||||
			this.peak = Math.max.apply(null, data.map(d => d.total));
 | 
			
		||||
			data.forEach(d => {
 | 
			
		||||
				d.postsH = d.posts / this.peak;
 | 
			
		||||
				d.notesH = d.notes / this.peak;
 | 
			
		||||
				d.repliesH = d.replies / this.peak;
 | 
			
		||||
				d.repostsH = d.reposts / this.peak;
 | 
			
		||||
				d.renotesH = d.renotes / this.peak;
 | 
			
		||||
			});
 | 
			
		||||
			data.reverse();
 | 
			
		||||
			this.data = data;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,16 @@ import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import ui from './ui.vue';
 | 
			
		||||
import timeline from './timeline.vue';
 | 
			
		||||
import post from './post.vue';
 | 
			
		||||
import posts from './posts.vue';
 | 
			
		||||
import note from './note.vue';
 | 
			
		||||
import notes from './notes.vue';
 | 
			
		||||
import mediaImage from './media-image.vue';
 | 
			
		||||
import mediaVideo from './media-video.vue';
 | 
			
		||||
import drive from './drive.vue';
 | 
			
		||||
import postPreview from './post-preview.vue';
 | 
			
		||||
import subPostContent from './sub-post-content.vue';
 | 
			
		||||
import postCard from './post-card.vue';
 | 
			
		||||
import notePreview from './note-preview.vue';
 | 
			
		||||
import subNoteContent from './sub-note-content.vue';
 | 
			
		||||
import noteCard from './note-card.vue';
 | 
			
		||||
import userCard from './user-card.vue';
 | 
			
		||||
import postDetail from './post-detail.vue';
 | 
			
		||||
import noteDetail from './note-detail.vue';
 | 
			
		||||
import followButton from './follow-button.vue';
 | 
			
		||||
import friendsMaker from './friends-maker.vue';
 | 
			
		||||
import notification from './notification.vue';
 | 
			
		||||
@@ -25,16 +25,16 @@ import widgetContainer from './widget-container.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-ui', ui);
 | 
			
		||||
Vue.component('mk-timeline', timeline);
 | 
			
		||||
Vue.component('mk-post', post);
 | 
			
		||||
Vue.component('mk-posts', posts);
 | 
			
		||||
Vue.component('mk-note', note);
 | 
			
		||||
Vue.component('mk-notes', notes);
 | 
			
		||||
Vue.component('mk-media-image', mediaImage);
 | 
			
		||||
Vue.component('mk-media-video', mediaVideo);
 | 
			
		||||
Vue.component('mk-drive', drive);
 | 
			
		||||
Vue.component('mk-post-preview', postPreview);
 | 
			
		||||
Vue.component('mk-sub-post-content', subPostContent);
 | 
			
		||||
Vue.component('mk-post-card', postCard);
 | 
			
		||||
Vue.component('mk-note-preview', notePreview);
 | 
			
		||||
Vue.component('mk-sub-note-content', subNoteContent);
 | 
			
		||||
Vue.component('mk-note-card', noteCard);
 | 
			
		||||
Vue.component('mk-user-card', userCard);
 | 
			
		||||
Vue.component('mk-post-detail', postDetail);
 | 
			
		||||
Vue.component('mk-note-detail', noteDetail);
 | 
			
		||||
Vue.component('mk-follow-button', followButton);
 | 
			
		||||
Vue.component('mk-friends-maker', friendsMaker);
 | 
			
		||||
Vue.component('mk-notification', notification);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,41 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-post-card">
 | 
			
		||||
	<a :href="`/@${acct}/${post.id}`">
 | 
			
		||||
<div class="mk-note-card">
 | 
			
		||||
	<a :href="`/@${acct}/${note.id}`">
 | 
			
		||||
		<header>
 | 
			
		||||
			<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div>
 | 
			
		||||
			{{ text }}
 | 
			
		||||
		</div>
 | 
			
		||||
		<mk-time :time="post.createdAt"/>
 | 
			
		||||
		<mk-time :time="note.createdAt"/>
 | 
			
		||||
	</a>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import summary from '../../../../../renderers/get-post-summary';
 | 
			
		||||
import summary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct() {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		text(): string {
 | 
			
		||||
			return summary(this.post);
 | 
			
		||||
			return summary(this.note);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-post-card
 | 
			
		||||
.mk-note-card
 | 
			
		||||
	display inline-block
 | 
			
		||||
	width 150px
 | 
			
		||||
	//height 120px
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="root sub">
 | 
			
		||||
	<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
	</router-link>
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<header>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link>
 | 
			
		||||
			<span class="username">@{{ acct }}</span>
 | 
			
		||||
			<router-link class="time" :to="`/@${acct}/${post.id}`">
 | 
			
		||||
				<mk-time :time="post.createdAt"/>
 | 
			
		||||
			<router-link class="time" :to="`/@${acct}/${note.id}`">
 | 
			
		||||
				<mk-time :time="note.createdAt"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<mk-sub-post-content class="text" :post="post"/>
 | 
			
		||||
			<mk-sub-note-content class="text" :note="note"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct() {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										462
									
								
								src/client/app/mobile/views/components/note-detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								src/client/app/mobile/views/components/note-detail.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,462 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-note-detail">
 | 
			
		||||
	<button
 | 
			
		||||
		class="more"
 | 
			
		||||
		v-if="p.reply && p.reply.replyId && context == null"
 | 
			
		||||
		@click="fetchContext"
 | 
			
		||||
		:disabled="fetchingContext"
 | 
			
		||||
	>
 | 
			
		||||
		<template v-if="!contextFetching">%fa:ellipsis-v%</template>
 | 
			
		||||
		<template v-if="contextFetching">%fa:spinner .pulse%</template>
 | 
			
		||||
	</button>
 | 
			
		||||
	<div class="context">
 | 
			
		||||
		<x-sub v-for="note in context" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="reply-to" v-if="p.reply">
 | 
			
		||||
		<x-sub :note="p.reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="renote" v-if="isRenote">
 | 
			
		||||
		<p>
 | 
			
		||||
			<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			%fa:retweet%
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">
 | 
			
		||||
				{{ name }}
 | 
			
		||||
			</router-link>
 | 
			
		||||
			がRenote
 | 
			
		||||
		</p>
 | 
			
		||||
	</div>
 | 
			
		||||
	<article>
 | 
			
		||||
		<header>
 | 
			
		||||
			<router-link class="avatar-anchor" :to="`/@${pAcct}`">
 | 
			
		||||
				<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			<div>
 | 
			
		||||
				<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
 | 
			
		||||
				<span class="username">@{{ pAcct }}</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
			<div class="tags" v-if="p.tags && p.tags.length > 0">
 | 
			
		||||
				<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="media" v-if="p.media.length > 0">
 | 
			
		||||
				<mk-media-list :media-list="p.media"/>
 | 
			
		||||
			</div>
 | 
			
		||||
			<mk-poll v-if="p.poll" :note="p"/>
 | 
			
		||||
			<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
			
		||||
			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
 | 
			
		||||
			<div class="map" v-if="p.geo" ref="map"></div>
 | 
			
		||||
			<div class="renote" v-if="p.renote">
 | 
			
		||||
				<mk-note-preview :note="p.renote"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<router-link class="time" :to="`/@${pAcct}/${p.id}`">
 | 
			
		||||
			<mk-time :time="p.createdAt" mode="detail"/>
 | 
			
		||||
		</router-link>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<mk-reactions-viewer :note="p"/>
 | 
			
		||||
			<button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%">
 | 
			
		||||
				%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button @click="renote" title="Renote">
 | 
			
		||||
				%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%">
 | 
			
		||||
				%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button @click="menu" ref="menuButton">
 | 
			
		||||
				%fa:ellipsis-h%
 | 
			
		||||
			</button>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</article>
 | 
			
		||||
	<div class="replies" v-if="!compact">
 | 
			
		||||
		<x-sub v-for="note in replies" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
import parse from '../../../../../text/parse';
 | 
			
		||||
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './note-detail.sub.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XSub
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		compact: {
 | 
			
		||||
			default: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			context: [],
 | 
			
		||||
			contextFetching: false,
 | 
			
		||||
			replies: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct(): string {
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name(): string {
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		pAcct(): string {
 | 
			
		||||
			return getAcct(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		pName(): string {
 | 
			
		||||
			return getUserName(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		isRenote(): boolean {
 | 
			
		||||
			return (this.note.renote &&
 | 
			
		||||
				this.note.text == null &&
 | 
			
		||||
				this.note.mediaIds == null &&
 | 
			
		||||
				this.note.poll == null);
 | 
			
		||||
		},
 | 
			
		||||
		p(): any {
 | 
			
		||||
			return this.isRenote ? this.note.renote : this.note;
 | 
			
		||||
		},
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
		urls(): string[] {
 | 
			
		||||
			if (this.p.text) {
 | 
			
		||||
				const ast = parse(this.p.text);
 | 
			
		||||
				return ast
 | 
			
		||||
					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 | 
			
		||||
					.map(t => t.url);
 | 
			
		||||
			} else {
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		// Get replies
 | 
			
		||||
		if (!this.compact) {
 | 
			
		||||
			(this as any).api('notes/replies', {
 | 
			
		||||
				noteId: this.p.id,
 | 
			
		||||
				limit: 8
 | 
			
		||||
			}).then(replies => {
 | 
			
		||||
				this.replies = replies;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Draw map
 | 
			
		||||
		if (this.p.geo) {
 | 
			
		||||
			const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
 | 
			
		||||
			if (shouldShowMap) {
 | 
			
		||||
				(this as any).os.getGoogleMaps().then(maps => {
 | 
			
		||||
					const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
 | 
			
		||||
					const map = new maps.Map(this.$refs.map, {
 | 
			
		||||
						center: uluru,
 | 
			
		||||
						zoom: 15
 | 
			
		||||
					});
 | 
			
		||||
					new maps.Marker({
 | 
			
		||||
						position: uluru,
 | 
			
		||||
						map: map
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetchContext() {
 | 
			
		||||
			this.contextFetching = true;
 | 
			
		||||
 | 
			
		||||
			// Fetch context
 | 
			
		||||
			(this as any).api('notes/context', {
 | 
			
		||||
				noteId: this.p.replyId
 | 
			
		||||
			}).then(context => {
 | 
			
		||||
				this.contextFetching = false;
 | 
			
		||||
				this.context = context.reverse();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		reply() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		renote() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		react() {
 | 
			
		||||
			(this as any).os.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		menu() {
 | 
			
		||||
			(this as any).os.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.mk-note-detail
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	padding 0
 | 
			
		||||
	width 100%
 | 
			
		||||
	text-align left
 | 
			
		||||
	background #fff
 | 
			
		||||
	border-radius 8px
 | 
			
		||||
	box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		padding 64px 0
 | 
			
		||||
 | 
			
		||||
	> .more
 | 
			
		||||
		display block
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 10px 0
 | 
			
		||||
		width 100%
 | 
			
		||||
		font-size 1em
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #999
 | 
			
		||||
		cursor pointer
 | 
			
		||||
		background #fafafa
 | 
			
		||||
		outline none
 | 
			
		||||
		border none
 | 
			
		||||
		border-bottom solid 1px #eef0f2
 | 
			
		||||
		border-radius 6px 6px 0 0
 | 
			
		||||
		box-shadow none
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			background #f6f6f6
 | 
			
		||||
 | 
			
		||||
		&:active
 | 
			
		||||
			background #f0f0f0
 | 
			
		||||
 | 
			
		||||
		&:disabled
 | 
			
		||||
			color #ccc
 | 
			
		||||
 | 
			
		||||
	> .context
 | 
			
		||||
		> *
 | 
			
		||||
			border-bottom 1px solid #eef0f2
 | 
			
		||||
 | 
			
		||||
	> .renote
 | 
			
		||||
		color #9dbb00
 | 
			
		||||
		background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
 | 
			
		||||
 | 
			
		||||
		> p
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px 32px
 | 
			
		||||
 | 
			
		||||
			.avatar-anchor
 | 
			
		||||
				display inline-block
 | 
			
		||||
 | 
			
		||||
				.avatar
 | 
			
		||||
					vertical-align bottom
 | 
			
		||||
					min-width 28px
 | 
			
		||||
					min-height 28px
 | 
			
		||||
					max-width 28px
 | 
			
		||||
					max-height 28px
 | 
			
		||||
					margin 0 8px 0 0
 | 
			
		||||
					border-radius 6px
 | 
			
		||||
 | 
			
		||||
			[data-fa]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 | 
			
		||||
			.name
 | 
			
		||||
				font-weight bold
 | 
			
		||||
 | 
			
		||||
		& + article
 | 
			
		||||
			padding-top 8px
 | 
			
		||||
 | 
			
		||||
	> .reply-to
 | 
			
		||||
		border-bottom 1px solid #eef0f2
 | 
			
		||||
 | 
			
		||||
	> article
 | 
			
		||||
		padding 14px 16px 9px 16px
 | 
			
		||||
 | 
			
		||||
		@media (min-width 500px)
 | 
			
		||||
			padding 28px 32px 18px 32px
 | 
			
		||||
 | 
			
		||||
		&:after
 | 
			
		||||
			content ""
 | 
			
		||||
			display block
 | 
			
		||||
			clear both
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			> .main > footer > button
 | 
			
		||||
				color #888
 | 
			
		||||
 | 
			
		||||
		> header
 | 
			
		||||
			display flex
 | 
			
		||||
			line-height 1.1
 | 
			
		||||
 | 
			
		||||
			> .avatar-anchor
 | 
			
		||||
				display block
 | 
			
		||||
				padding 0 .5em 0 0
 | 
			
		||||
 | 
			
		||||
				> .avatar
 | 
			
		||||
					display block
 | 
			
		||||
					width 54px
 | 
			
		||||
					height 54px
 | 
			
		||||
					margin 0
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
					vertical-align bottom
 | 
			
		||||
 | 
			
		||||
					@media (min-width 500px)
 | 
			
		||||
						width 60px
 | 
			
		||||
						height 60px
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
 | 
			
		||||
				> .name
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin .4em 0
 | 
			
		||||
					color #777
 | 
			
		||||
					font-size 16px
 | 
			
		||||
					font-weight bold
 | 
			
		||||
					text-align left
 | 
			
		||||
					text-decoration none
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						text-decoration underline
 | 
			
		||||
 | 
			
		||||
				> .username
 | 
			
		||||
					display block
 | 
			
		||||
					text-align left
 | 
			
		||||
					margin 0
 | 
			
		||||
					color #ccc
 | 
			
		||||
 | 
			
		||||
		> .body
 | 
			
		||||
			padding 8px 0
 | 
			
		||||
 | 
			
		||||
			> .renote
 | 
			
		||||
				margin 8px 0
 | 
			
		||||
 | 
			
		||||
				> .mk-note-preview
 | 
			
		||||
					padding 16px
 | 
			
		||||
					border dashed 1px #c0dac6
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
 | 
			
		||||
			> .location
 | 
			
		||||
				margin 4px 0
 | 
			
		||||
				font-size 12px
 | 
			
		||||
				color #ccc
 | 
			
		||||
 | 
			
		||||
			> .map
 | 
			
		||||
				width 100%
 | 
			
		||||
				height 200px
 | 
			
		||||
 | 
			
		||||
				&:empty
 | 
			
		||||
					display none
 | 
			
		||||
 | 
			
		||||
			> .mk-url-preview
 | 
			
		||||
				margin-top 8px
 | 
			
		||||
 | 
			
		||||
			> .media
 | 
			
		||||
				> img
 | 
			
		||||
					display block
 | 
			
		||||
					max-width 100%
 | 
			
		||||
 | 
			
		||||
			> .tags
 | 
			
		||||
				margin 4px 0 0 0
 | 
			
		||||
 | 
			
		||||
				> *
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin 0 8px 0 0
 | 
			
		||||
					padding 2px 8px 2px 16px
 | 
			
		||||
					font-size 90%
 | 
			
		||||
					color #8d969e
 | 
			
		||||
					background #edf0f3
 | 
			
		||||
					border-radius 4px
 | 
			
		||||
 | 
			
		||||
					&:before
 | 
			
		||||
						content ""
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						top 0
 | 
			
		||||
						bottom 0
 | 
			
		||||
						left 4px
 | 
			
		||||
						width 8px
 | 
			
		||||
						height 8px
 | 
			
		||||
						margin auto 0
 | 
			
		||||
						background #fff
 | 
			
		||||
						border-radius 100%
 | 
			
		||||
 | 
			
		||||
		> .time
 | 
			
		||||
			font-size 16px
 | 
			
		||||
			color #c0c0c0
 | 
			
		||||
 | 
			
		||||
		> footer
 | 
			
		||||
			font-size 1.2em
 | 
			
		||||
 | 
			
		||||
			> button
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 8px
 | 
			
		||||
				background transparent
 | 
			
		||||
				border none
 | 
			
		||||
				box-shadow none
 | 
			
		||||
				font-size 1em
 | 
			
		||||
				color #ddd
 | 
			
		||||
				cursor pointer
 | 
			
		||||
 | 
			
		||||
				&:not(:last-child)
 | 
			
		||||
					margin-right 28px
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color #666
 | 
			
		||||
 | 
			
		||||
				> .count
 | 
			
		||||
					display inline
 | 
			
		||||
					margin 0 0 0 8px
 | 
			
		||||
					color #999
 | 
			
		||||
 | 
			
		||||
				&.reacted
 | 
			
		||||
					color $theme-color
 | 
			
		||||
 | 
			
		||||
	> .replies
 | 
			
		||||
		> *
 | 
			
		||||
			border-top 1px solid #eef0f2
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.text
 | 
			
		||||
	display block
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 0
 | 
			
		||||
	overflow-wrap break-word
 | 
			
		||||
	font-size 16px
 | 
			
		||||
	color #717171
 | 
			
		||||
 | 
			
		||||
	@media (min-width 500px)
 | 
			
		||||
		font-size 24px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-post-preview">
 | 
			
		||||
<div class="mk-note-preview">
 | 
			
		||||
	<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
	</router-link>
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<header>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 | 
			
		||||
			<span class="username">@{{ acct }}</span>
 | 
			
		||||
			<router-link class="time" :to="`/@${acct}/${post.id}`">
 | 
			
		||||
				<mk-time :time="post.createdAt"/>
 | 
			
		||||
			<router-link class="time" :to="`/@${acct}/${note.id}`">
 | 
			
		||||
				<mk-time :time="note.createdAt"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<mk-sub-post-content class="text" :post="post"/>
 | 
			
		||||
			<mk-sub-note-content class="text" :note="note"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -24,20 +24,20 @@ import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct() {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-post-preview
 | 
			
		||||
.mk-note-preview
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 0
 | 
			
		||||
	font-size 0.9em
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="sub">
 | 
			
		||||
	<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
		<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
 | 
			
		||||
	</router-link>
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<header>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ getUserName(note.user) }}</router-link>
 | 
			
		||||
			<span class="username">@{{ acct }}</span>
 | 
			
		||||
			<router-link class="created-at" :to="`/@${acct}/${post.id}`">
 | 
			
		||||
				<mk-time :time="post.createdAt"/>
 | 
			
		||||
			<router-link class="created-at" :to="`/@${acct}/${note.id}`">
 | 
			
		||||
				<mk-time :time="note.createdAt"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<mk-sub-post-content class="text" :post="post"/>
 | 
			
		||||
			<mk-sub-note-content class="text" :note="note"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -24,13 +24,13 @@ import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct() {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										540
									
								
								src/client/app/mobile/views/components/note.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								src/client/app/mobile/views/components/note.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,540 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="note" :class="{ renote: isRenote }">
 | 
			
		||||
	<div class="reply-to" v-if="p.reply">
 | 
			
		||||
		<x-sub :note="p.reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="renote" v-if="isRenote">
 | 
			
		||||
		<p>
 | 
			
		||||
			<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			%fa:retweet%
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
 | 
			
		||||
		</p>
 | 
			
		||||
		<mk-time :time="note.createdAt"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<article>
 | 
			
		||||
		<router-link class="avatar-anchor" :to="`/@${pAcct}`">
 | 
			
		||||
			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
 | 
			
		||||
		</router-link>
 | 
			
		||||
		<div class="main">
 | 
			
		||||
			<header>
 | 
			
		||||
				<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
 | 
			
		||||
				<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
 | 
			
		||||
				<span class="username">@{{ pAcct }}</span>
 | 
			
		||||
				<div class="info">
 | 
			
		||||
					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
 | 
			
		||||
					<router-link class="created-at" :to="url">
 | 
			
		||||
						<mk-time :time="p.createdAt"/>
 | 
			
		||||
					</router-link>
 | 
			
		||||
				</div>
 | 
			
		||||
			</header>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
 | 
			
		||||
				<div class="text">
 | 
			
		||||
					<a class="reply" v-if="p.reply">
 | 
			
		||||
						%fa:reply%
 | 
			
		||||
					</a>
 | 
			
		||||
					<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
					<a class="rp" v-if="p.renote != null">RP:</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="media" v-if="p.media.length > 0">
 | 
			
		||||
					<mk-media-list :media-list="p.media"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
 | 
			
		||||
				<div class="tags" v-if="p.tags && p.tags.length > 0">
 | 
			
		||||
					<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
 | 
			
		||||
				</div>
 | 
			
		||||
				<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
			
		||||
				<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
 | 
			
		||||
				<div class="map" v-if="p.geo" ref="map"></div>
 | 
			
		||||
				<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
 | 
			
		||||
				<div class="renote" v-if="p.renote">
 | 
			
		||||
					<mk-note-preview :note="p.renote"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<footer>
 | 
			
		||||
				<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
 | 
			
		||||
				<button @click="reply">
 | 
			
		||||
					%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button @click="renote" title="Renote">
 | 
			
		||||
					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
 | 
			
		||||
					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button class="menu" @click="menu" ref="menuButton">
 | 
			
		||||
					%fa:ellipsis-h%
 | 
			
		||||
				</button>
 | 
			
		||||
			</footer>
 | 
			
		||||
		</div>
 | 
			
		||||
	</article>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
import parse from '../../../../../text/parse';
 | 
			
		||||
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './note.sub.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XSub
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct(): string {
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name(): string {
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		pAcct(): string {
 | 
			
		||||
			return getAcct(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		pName(): string {
 | 
			
		||||
			return getUserName(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		isRenote(): boolean {
 | 
			
		||||
			return (this.note.renote &&
 | 
			
		||||
				this.note.text == null &&
 | 
			
		||||
				this.note.mediaIds == null &&
 | 
			
		||||
				this.note.poll == null);
 | 
			
		||||
		},
 | 
			
		||||
		p(): any {
 | 
			
		||||
			return this.isRenote ? this.note.renote : this.note;
 | 
			
		||||
		},
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
		url(): string {
 | 
			
		||||
			return `/@${this.pAcct}/${this.p.id}`;
 | 
			
		||||
		},
 | 
			
		||||
		urls(): string[] {
 | 
			
		||||
			if (this.p.text) {
 | 
			
		||||
				const ast = parse(this.p.text);
 | 
			
		||||
				return ast
 | 
			
		||||
					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 | 
			
		||||
					.map(t => t.url);
 | 
			
		||||
			} else {
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		if ((this as any).os.isSignedIn) {
 | 
			
		||||
			this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
			this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.capture(true);
 | 
			
		||||
 | 
			
		||||
		if ((this as any).os.isSignedIn) {
 | 
			
		||||
			this.connection.on('_connected_', this.onStreamConnected);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Draw map
 | 
			
		||||
		if (this.p.geo) {
 | 
			
		||||
			const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
 | 
			
		||||
			if (shouldShowMap) {
 | 
			
		||||
				(this as any).os.getGoogleMaps().then(maps => {
 | 
			
		||||
					const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
 | 
			
		||||
					const map = new maps.Map(this.$refs.map, {
 | 
			
		||||
						center: uluru,
 | 
			
		||||
						zoom: 15
 | 
			
		||||
					});
 | 
			
		||||
					new maps.Marker({
 | 
			
		||||
						position: uluru,
 | 
			
		||||
						map: map
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.decapture(true);
 | 
			
		||||
 | 
			
		||||
		if ((this as any).os.isSignedIn) {
 | 
			
		||||
			this.connection.off('_connected_', this.onStreamConnected);
 | 
			
		||||
			(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		capture(withHandler = false) {
 | 
			
		||||
			if ((this as any).os.isSignedIn) {
 | 
			
		||||
				this.connection.send({
 | 
			
		||||
					type: 'capture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		decapture(withHandler = false) {
 | 
			
		||||
			if ((this as any).os.isSignedIn) {
 | 
			
		||||
				this.connection.send({
 | 
			
		||||
					type: 'decapture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onStreamConnected() {
 | 
			
		||||
			this.capture();
 | 
			
		||||
		},
 | 
			
		||||
		onStreamNoteUpdated(data) {
 | 
			
		||||
			const note = data.note;
 | 
			
		||||
			if (note.id == this.note.id) {
 | 
			
		||||
				this.$emit('update:note', note);
 | 
			
		||||
			} else if (note.id == this.note.renoteId) {
 | 
			
		||||
				this.note.renote = note;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		reply() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		renote() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		react() {
 | 
			
		||||
			(this as any).os.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		menu() {
 | 
			
		||||
			(this as any).os.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.note
 | 
			
		||||
	font-size 12px
 | 
			
		||||
	border-bottom solid 1px #eaeaea
 | 
			
		||||
 | 
			
		||||
	&:first-child
 | 
			
		||||
		border-radius 8px 8px 0 0
 | 
			
		||||
 | 
			
		||||
		> .renote
 | 
			
		||||
			border-radius 8px 8px 0 0
 | 
			
		||||
 | 
			
		||||
	&:last-of-type
 | 
			
		||||
		border-bottom none
 | 
			
		||||
 | 
			
		||||
	@media (min-width 350px)
 | 
			
		||||
		font-size 14px
 | 
			
		||||
 | 
			
		||||
	@media (min-width 500px)
 | 
			
		||||
		font-size 16px
 | 
			
		||||
 | 
			
		||||
	> .renote
 | 
			
		||||
		color #9dbb00
 | 
			
		||||
		background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
 | 
			
		||||
 | 
			
		||||
		> p
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 8px 16px
 | 
			
		||||
			line-height 28px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				padding 16px
 | 
			
		||||
 | 
			
		||||
			.avatar-anchor
 | 
			
		||||
				display inline-block
 | 
			
		||||
 | 
			
		||||
				.avatar
 | 
			
		||||
					vertical-align bottom
 | 
			
		||||
					width 28px
 | 
			
		||||
					height 28px
 | 
			
		||||
					margin 0 8px 0 0
 | 
			
		||||
					border-radius 6px
 | 
			
		||||
 | 
			
		||||
			[data-fa]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 | 
			
		||||
			.name
 | 
			
		||||
				font-weight bold
 | 
			
		||||
 | 
			
		||||
		> .mk-time
 | 
			
		||||
			position absolute
 | 
			
		||||
			top 8px
 | 
			
		||||
			right 16px
 | 
			
		||||
			font-size 0.9em
 | 
			
		||||
			line-height 28px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				top 16px
 | 
			
		||||
 | 
			
		||||
		& + article
 | 
			
		||||
			padding-top 8px
 | 
			
		||||
 | 
			
		||||
	> .reply-to
 | 
			
		||||
		background rgba(0, 0, 0, 0.0125)
 | 
			
		||||
 | 
			
		||||
		> .mk-note-preview
 | 
			
		||||
			background transparent
 | 
			
		||||
 | 
			
		||||
	> article
 | 
			
		||||
		padding 14px 16px 9px 16px
 | 
			
		||||
 | 
			
		||||
		&:after
 | 
			
		||||
			content ""
 | 
			
		||||
			display block
 | 
			
		||||
			clear both
 | 
			
		||||
 | 
			
		||||
		> .avatar-anchor
 | 
			
		||||
			display block
 | 
			
		||||
			float left
 | 
			
		||||
			margin 0 10px 8px 0
 | 
			
		||||
			position -webkit-sticky
 | 
			
		||||
			position sticky
 | 
			
		||||
			top 62px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				margin-right 16px
 | 
			
		||||
 | 
			
		||||
			> .avatar
 | 
			
		||||
				display block
 | 
			
		||||
				width 48px
 | 
			
		||||
				height 48px
 | 
			
		||||
				margin 0
 | 
			
		||||
				border-radius 6px
 | 
			
		||||
				vertical-align bottom
 | 
			
		||||
 | 
			
		||||
				@media (min-width 500px)
 | 
			
		||||
					width 58px
 | 
			
		||||
					height 58px
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
 | 
			
		||||
		> .main
 | 
			
		||||
			float left
 | 
			
		||||
			width calc(100% - 58px)
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				width calc(100% - 74px)
 | 
			
		||||
 | 
			
		||||
			> header
 | 
			
		||||
				display flex
 | 
			
		||||
				align-items center
 | 
			
		||||
				white-space nowrap
 | 
			
		||||
 | 
			
		||||
				@media (min-width 500px)
 | 
			
		||||
					margin-bottom 2px
 | 
			
		||||
 | 
			
		||||
				> .name
 | 
			
		||||
					display block
 | 
			
		||||
					margin 0 0.5em 0 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					overflow hidden
 | 
			
		||||
					color #627079
 | 
			
		||||
					font-size 1em
 | 
			
		||||
					font-weight bold
 | 
			
		||||
					text-decoration none
 | 
			
		||||
					text-overflow ellipsis
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						text-decoration underline
 | 
			
		||||
 | 
			
		||||
				> .is-bot
 | 
			
		||||
					margin 0 0.5em 0 0
 | 
			
		||||
					padding 1px 6px
 | 
			
		||||
					font-size 12px
 | 
			
		||||
					color #aaa
 | 
			
		||||
					border solid 1px #ddd
 | 
			
		||||
					border-radius 3px
 | 
			
		||||
 | 
			
		||||
				> .username
 | 
			
		||||
					margin 0 0.5em 0 0
 | 
			
		||||
					color #ccc
 | 
			
		||||
 | 
			
		||||
				> .info
 | 
			
		||||
					margin-left auto
 | 
			
		||||
					font-size 0.9em
 | 
			
		||||
 | 
			
		||||
					> .mobile
 | 
			
		||||
						margin-right 6px
 | 
			
		||||
						color #c0c0c0
 | 
			
		||||
 | 
			
		||||
					> .created-at
 | 
			
		||||
						color #c0c0c0
 | 
			
		||||
 | 
			
		||||
			> .body
 | 
			
		||||
 | 
			
		||||
				> .text
 | 
			
		||||
					display block
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					overflow-wrap break-word
 | 
			
		||||
					font-size 1.1em
 | 
			
		||||
					color #717171
 | 
			
		||||
 | 
			
		||||
					>>> .quote
 | 
			
		||||
						margin 8px
 | 
			
		||||
						padding 6px 12px
 | 
			
		||||
						color #aaa
 | 
			
		||||
						border-left solid 3px #eee
 | 
			
		||||
 | 
			
		||||
					> .reply
 | 
			
		||||
						margin-right 8px
 | 
			
		||||
						color #717171
 | 
			
		||||
 | 
			
		||||
					> .rp
 | 
			
		||||
						margin-left 4px
 | 
			
		||||
						font-style oblique
 | 
			
		||||
						color #a0bf46
 | 
			
		||||
 | 
			
		||||
					[data-is-me]:after
 | 
			
		||||
						content "you"
 | 
			
		||||
						padding 0 4px
 | 
			
		||||
						margin-left 4px
 | 
			
		||||
						font-size 80%
 | 
			
		||||
						color $theme-color-foreground
 | 
			
		||||
						background $theme-color
 | 
			
		||||
						border-radius 4px
 | 
			
		||||
 | 
			
		||||
				.mk-url-preview
 | 
			
		||||
					margin-top 8px
 | 
			
		||||
 | 
			
		||||
				> .channel
 | 
			
		||||
					margin 0
 | 
			
		||||
 | 
			
		||||
				> .tags
 | 
			
		||||
					margin 4px 0 0 0
 | 
			
		||||
 | 
			
		||||
					> *
 | 
			
		||||
						display inline-block
 | 
			
		||||
						margin 0 8px 0 0
 | 
			
		||||
						padding 2px 8px 2px 16px
 | 
			
		||||
						font-size 90%
 | 
			
		||||
						color #8d969e
 | 
			
		||||
						background #edf0f3
 | 
			
		||||
						border-radius 4px
 | 
			
		||||
 | 
			
		||||
						&:before
 | 
			
		||||
							content ""
 | 
			
		||||
							display block
 | 
			
		||||
							position absolute
 | 
			
		||||
							top 0
 | 
			
		||||
							bottom 0
 | 
			
		||||
							left 4px
 | 
			
		||||
							width 8px
 | 
			
		||||
							height 8px
 | 
			
		||||
							margin auto 0
 | 
			
		||||
							background #fff
 | 
			
		||||
							border-radius 100%
 | 
			
		||||
 | 
			
		||||
				> .media
 | 
			
		||||
					> img
 | 
			
		||||
						display block
 | 
			
		||||
						max-width 100%
 | 
			
		||||
 | 
			
		||||
				> .location
 | 
			
		||||
					margin 4px 0
 | 
			
		||||
					font-size 12px
 | 
			
		||||
					color #ccc
 | 
			
		||||
 | 
			
		||||
				> .map
 | 
			
		||||
					width 100%
 | 
			
		||||
					height 200px
 | 
			
		||||
 | 
			
		||||
					&:empty
 | 
			
		||||
						display none
 | 
			
		||||
 | 
			
		||||
				> .app
 | 
			
		||||
					font-size 12px
 | 
			
		||||
					color #ccc
 | 
			
		||||
 | 
			
		||||
				> .mk-poll
 | 
			
		||||
					font-size 80%
 | 
			
		||||
 | 
			
		||||
				> .renote
 | 
			
		||||
					margin 8px 0
 | 
			
		||||
 | 
			
		||||
					> .mk-note-preview
 | 
			
		||||
						padding 16px
 | 
			
		||||
						border dashed 1px #c0dac6
 | 
			
		||||
						border-radius 8px
 | 
			
		||||
 | 
			
		||||
			> footer
 | 
			
		||||
				> button
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 8px
 | 
			
		||||
					background transparent
 | 
			
		||||
					border none
 | 
			
		||||
					box-shadow none
 | 
			
		||||
					font-size 1em
 | 
			
		||||
					color #ddd
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
					&:not(:last-child)
 | 
			
		||||
						margin-right 28px
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						color #666
 | 
			
		||||
 | 
			
		||||
					> .count
 | 
			
		||||
						display inline
 | 
			
		||||
						margin 0 0 0 8px
 | 
			
		||||
						color #999
 | 
			
		||||
 | 
			
		||||
					&.reacted
 | 
			
		||||
						color $theme-color
 | 
			
		||||
 | 
			
		||||
					&.menu
 | 
			
		||||
						@media (max-width 350px)
 | 
			
		||||
							display none
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.text
 | 
			
		||||
	code
 | 
			
		||||
		padding 4px 8px
 | 
			
		||||
		margin 0 0.5em
 | 
			
		||||
		font-size 80%
 | 
			
		||||
		color #525252
 | 
			
		||||
		background #f8f8f8
 | 
			
		||||
		border-radius 2px
 | 
			
		||||
 | 
			
		||||
	pre > code
 | 
			
		||||
		padding 16px
 | 
			
		||||
		margin 0
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-posts">
 | 
			
		||||
<div class="mk-notes">
 | 
			
		||||
	<slot name="head"></slot>
 | 
			
		||||
	<slot></slot>
 | 
			
		||||
	<template v-for="(post, i) in _posts">
 | 
			
		||||
		<mk-post :post="post" :key="post.id" @update:post="onPostUpdated(i, $event)"/>
 | 
			
		||||
		<p class="date" v-if="i != posts.length - 1 && post._date != _posts[i + 1]._date">
 | 
			
		||||
			<span>%fa:angle-up%{{ post._datetext }}</span>
 | 
			
		||||
			<span>%fa:angle-down%{{ _posts[i + 1]._datetext }}</span>
 | 
			
		||||
	<template v-for="(note, i) in _notes">
 | 
			
		||||
		<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
 | 
			
		||||
		<p class="date" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
			
		||||
			<span>%fa:angle-up%{{ note._datetext }}</span>
 | 
			
		||||
			<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
 | 
			
		||||
		</p>
 | 
			
		||||
	</template>
 | 
			
		||||
	<footer>
 | 
			
		||||
@@ -20,25 +20,25 @@ import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		posts: {
 | 
			
		||||
		notes: {
 | 
			
		||||
			type: Array,
 | 
			
		||||
			default: () => []
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		_posts(): any[] {
 | 
			
		||||
			return (this.posts as any).map(post => {
 | 
			
		||||
				const date = new Date(post.createdAt).getDate();
 | 
			
		||||
				const month = new Date(post.createdAt).getMonth() + 1;
 | 
			
		||||
				post._date = date;
 | 
			
		||||
				post._datetext = `${month}月 ${date}日`;
 | 
			
		||||
				return post;
 | 
			
		||||
		_notes(): any[] {
 | 
			
		||||
			return (this.notes as any).map(note => {
 | 
			
		||||
				const date = new Date(note.createdAt).getDate();
 | 
			
		||||
				const month = new Date(note.createdAt).getMonth() + 1;
 | 
			
		||||
				note._date = date;
 | 
			
		||||
				note._datetext = `${month}月 ${date}日`;
 | 
			
		||||
				return note;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		onPostUpdated(i, post) {
 | 
			
		||||
			Vue.set((this as any).posts, i, post);
 | 
			
		||||
		onNoteUpdated(i, note) {
 | 
			
		||||
			Vue.set((this as any).notes, i, note);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -47,7 +47,7 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.mk-posts
 | 
			
		||||
.mk-notes
 | 
			
		||||
	background #fff
 | 
			
		||||
	border-radius 8px
 | 
			
		||||
	box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
 | 
			
		||||
@@ -4,23 +4,23 @@
 | 
			
		||||
		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p>
 | 
			
		||||
			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
 | 
			
		||||
			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'repost'">
 | 
			
		||||
		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
	<template v-if="notification.type == 'renote'">
 | 
			
		||||
		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>%fa:retweet%{{ posterName }}</p>
 | 
			
		||||
			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p>
 | 
			
		||||
			<p>%fa:retweet%{{ noteerName }}</p>
 | 
			
		||||
			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'quote'">
 | 
			
		||||
		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>%fa:quote-left%{{ posterName }}</p>
 | 
			
		||||
			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 | 
			
		||||
			<p>%fa:quote-left%{{ noteerName }}</p>
 | 
			
		||||
			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
@@ -32,18 +32,18 @@
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'reply'">
 | 
			
		||||
		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>%fa:reply%{{ posterName }}</p>
 | 
			
		||||
			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 | 
			
		||||
			<p>%fa:reply%{{ noteerName }}</p>
 | 
			
		||||
			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'mention'">
 | 
			
		||||
		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>%fa:at%{{ posterName }}</p>
 | 
			
		||||
			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 | 
			
		||||
			<p>%fa:at%{{ noteerName }}</p>
 | 
			
		||||
			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>%fa:chart-pie%{{ name }}</p>
 | 
			
		||||
			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
 | 
			
		||||
			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -59,7 +59,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getPostSummary from '../../../../../renderers/get-post-summary';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
@@ -68,13 +68,13 @@ export default Vue.extend({
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.notification.user);
 | 
			
		||||
		},
 | 
			
		||||
		posterName() {
 | 
			
		||||
			return getUserName(this.notification.post.user);
 | 
			
		||||
		noteerName() {
 | 
			
		||||
			return getUserName(this.notification.note.user);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			getPostSummary
 | 
			
		||||
			getNoteSummary
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -112,7 +112,7 @@ export default Vue.extend({
 | 
			
		||||
			i, mk-reaction-icon
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 | 
			
		||||
	.post-ref
 | 
			
		||||
	.note-ref
 | 
			
		||||
 | 
			
		||||
		[data-fa]
 | 
			
		||||
			font-size 1em
 | 
			
		||||
@@ -121,7 +121,7 @@ export default Vue.extend({
 | 
			
		||||
			display inline-block
 | 
			
		||||
			margin-right 3px
 | 
			
		||||
 | 
			
		||||
	&.repost, &.quote
 | 
			
		||||
	&.renote, &.quote
 | 
			
		||||
		.text p i
 | 
			
		||||
			color #77B255
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,31 +10,31 @@
 | 
			
		||||
				<mk-reaction-icon :reaction="notification.reaction"/>
 | 
			
		||||
				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
 | 
			
		||||
			</p>
 | 
			
		||||
			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getPostSummary(notification.post) }}
 | 
			
		||||
			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getNoteSummary(notification.note) }}
 | 
			
		||||
				%fa:quote-right%
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="notification repost" v-if="notification.type == 'repost'">
 | 
			
		||||
	<div class="notification renote" v-if="notification.type == 'renote'">
 | 
			
		||||
		<mk-time :time="notification.createdAt"/>
 | 
			
		||||
		<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
			<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
			<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
		</router-link>
 | 
			
		||||
		<div class="text">
 | 
			
		||||
			<p>
 | 
			
		||||
				%fa:retweet%
 | 
			
		||||
				<router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link>
 | 
			
		||||
				<router-link :to="`/@${acct}`">{{ getUserName(notification.note.user) }}</router-link>
 | 
			
		||||
			</p>
 | 
			
		||||
			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%
 | 
			
		||||
			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'quote'">
 | 
			
		||||
		<mk-post :post="notification.post"/>
 | 
			
		||||
		<mk-note :note="notification.note"/>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<div class="notification follow" v-if="notification.type == 'follow'">
 | 
			
		||||
@@ -51,11 +51,11 @@
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'reply'">
 | 
			
		||||
		<mk-post :post="notification.post"/>
 | 
			
		||||
		<mk-note :note="notification.note"/>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template v-if="notification.type == 'mention'">
 | 
			
		||||
		<mk-post :post="notification.post"/>
 | 
			
		||||
		<mk-note :note="notification.note"/>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
 | 
			
		||||
@@ -68,8 +68,8 @@
 | 
			
		||||
				%fa:chart-pie%
 | 
			
		||||
				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
 | 
			
		||||
			</p>
 | 
			
		||||
			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%
 | 
			
		||||
			<router-link class="note-ref" :to="`/@${acct}/${notification.note.id}`">
 | 
			
		||||
				%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%
 | 
			
		||||
			</router-link>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -78,7 +78,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getPostSummary from '../../../../../renderers/get-post-summary';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
 | 
			
		||||
@@ -91,13 +91,13 @@ export default Vue.extend({
 | 
			
		||||
		name() {
 | 
			
		||||
			return getUserName(this.notification.user);
 | 
			
		||||
		},
 | 
			
		||||
		posterName() {
 | 
			
		||||
 			return getUserName(this.notification.post.user);
 | 
			
		||||
		noteerName() {
 | 
			
		||||
 			return getUserName(this.notification.note.user);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			getPostSummary
 | 
			
		||||
			getNoteSummary
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -146,10 +146,10 @@ export default Vue.extend({
 | 
			
		||||
				i, .mk-reaction-icon
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
 | 
			
		||||
			> .post-preview
 | 
			
		||||
			> .note-preview
 | 
			
		||||
				color rgba(0, 0, 0, 0.7)
 | 
			
		||||
 | 
			
		||||
			> .post-ref
 | 
			
		||||
			> .note-ref
 | 
			
		||||
				color rgba(0, 0, 0, 0.7)
 | 
			
		||||
 | 
			
		||||
				[data-fa]
 | 
			
		||||
@@ -159,7 +159,7 @@ export default Vue.extend({
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin-right 3px
 | 
			
		||||
 | 
			
		||||
		&.repost
 | 
			
		||||
		&.renote
 | 
			
		||||
			.text p i
 | 
			
		||||
				color #77B255
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-post-detail">
 | 
			
		||||
<div class="mk-note-detail">
 | 
			
		||||
	<button
 | 
			
		||||
		class="more"
 | 
			
		||||
		v-if="p.reply && p.reply.replyId && context == null"
 | 
			
		||||
@@ -10,21 +10,21 @@
 | 
			
		||||
		<template v-if="contextFetching">%fa:spinner .pulse%</template>
 | 
			
		||||
	</button>
 | 
			
		||||
	<div class="context">
 | 
			
		||||
		<x-sub v-for="post in context" :key="post.id" :post="post"/>
 | 
			
		||||
		<x-sub v-for="note in context" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="reply-to" v-if="p.reply">
 | 
			
		||||
		<x-sub :post="p.reply"/>
 | 
			
		||||
		<x-sub :note="p.reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="repost" v-if="isRepost">
 | 
			
		||||
	<div class="renote" v-if="isRenote">
 | 
			
		||||
		<p>
 | 
			
		||||
			<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
 | 
			
		||||
				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			%fa:retweet%
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">
 | 
			
		||||
				{{ name }}
 | 
			
		||||
			</router-link>
 | 
			
		||||
			がRepost
 | 
			
		||||
			がRenote
 | 
			
		||||
		</p>
 | 
			
		||||
	</div>
 | 
			
		||||
	<article>
 | 
			
		||||
@@ -38,33 +38,33 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<mk-post-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
			<mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
			<div class="tags" v-if="p.tags && p.tags.length > 0">
 | 
			
		||||
				<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="media" v-if="p.media.length > 0">
 | 
			
		||||
				<mk-media-list :media-list="p.media"/>
 | 
			
		||||
			</div>
 | 
			
		||||
			<mk-poll v-if="p.poll" :post="p"/>
 | 
			
		||||
			<mk-poll v-if="p.poll" :note="p"/>
 | 
			
		||||
			<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
			
		||||
			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
 | 
			
		||||
			<div class="map" v-if="p.geo" ref="map"></div>
 | 
			
		||||
			<div class="repost" v-if="p.repost">
 | 
			
		||||
				<mk-post-preview :post="p.repost"/>
 | 
			
		||||
			<div class="renote" v-if="p.renote">
 | 
			
		||||
				<mk-note-preview :note="p.renote"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<router-link class="time" :to="`/@${pAcct}/${p.id}`">
 | 
			
		||||
			<mk-time :time="p.createdAt" mode="detail"/>
 | 
			
		||||
		</router-link>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<mk-reactions-viewer :post="p"/>
 | 
			
		||||
			<button @click="reply" title="%i18n:mobile.tags.mk-post-detail.reply%">
 | 
			
		||||
			<mk-reactions-viewer :note="p"/>
 | 
			
		||||
			<button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%">
 | 
			
		||||
				%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button @click="repost" title="Repost">
 | 
			
		||||
				%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p>
 | 
			
		||||
			<button @click="renote" title="Renote">
 | 
			
		||||
				%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%">
 | 
			
		||||
			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%">
 | 
			
		||||
				%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button @click="menu" ref="menuButton">
 | 
			
		||||
@@ -73,7 +73,7 @@
 | 
			
		||||
		</footer>
 | 
			
		||||
	</article>
 | 
			
		||||
	<div class="replies" v-if="!compact">
 | 
			
		||||
		<x-sub v-for="post in replies" :key="post.id" :post="post"/>
 | 
			
		||||
		<x-sub v-for="note in replies" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -84,9 +84,9 @@ import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
import parse from '../../../../../text/parse';
 | 
			
		||||
 | 
			
		||||
import MkPostMenu from '../../../common/views/components/post-menu.vue';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './post-detail.sub.vue';
 | 
			
		||||
import XSub from './note-detail.sub.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -94,7 +94,7 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		post: {
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
@@ -113,10 +113,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct(): string {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name(): string {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		pAcct(): string {
 | 
			
		||||
			return getAcct(this.p.user);
 | 
			
		||||
@@ -124,14 +124,14 @@ export default Vue.extend({
 | 
			
		||||
		pName(): string {
 | 
			
		||||
			return getUserName(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		isRepost(): boolean {
 | 
			
		||||
			return (this.post.repost &&
 | 
			
		||||
				this.post.text == null &&
 | 
			
		||||
				this.post.mediaIds == null &&
 | 
			
		||||
				this.post.poll == null);
 | 
			
		||||
		isRenote(): boolean {
 | 
			
		||||
			return (this.note.renote &&
 | 
			
		||||
				this.note.text == null &&
 | 
			
		||||
				this.note.mediaIds == null &&
 | 
			
		||||
				this.note.poll == null);
 | 
			
		||||
		},
 | 
			
		||||
		p(): any {
 | 
			
		||||
			return this.isRepost ? this.post.repost : this.post;
 | 
			
		||||
			return this.isRenote ? this.note.renote : this.note;
 | 
			
		||||
		},
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
@@ -155,8 +155,8 @@ export default Vue.extend({
 | 
			
		||||
	mounted() {
 | 
			
		||||
		// Get replies
 | 
			
		||||
		if (!this.compact) {
 | 
			
		||||
			(this as any).api('posts/replies', {
 | 
			
		||||
				postId: this.p.id,
 | 
			
		||||
			(this as any).api('notes/replies', {
 | 
			
		||||
				noteId: this.p.id,
 | 
			
		||||
				limit: 8
 | 
			
		||||
			}).then(replies => {
 | 
			
		||||
				this.replies = replies;
 | 
			
		||||
@@ -187,8 +187,8 @@ export default Vue.extend({
 | 
			
		||||
			this.contextFetching = true;
 | 
			
		||||
 | 
			
		||||
			// Fetch context
 | 
			
		||||
			(this as any).api('posts/context', {
 | 
			
		||||
				postId: this.p.replyId
 | 
			
		||||
			(this as any).api('notes/context', {
 | 
			
		||||
				noteId: this.p.replyId
 | 
			
		||||
			}).then(context => {
 | 
			
		||||
				this.contextFetching = false;
 | 
			
		||||
				this.context = context.reverse();
 | 
			
		||||
@@ -199,22 +199,22 @@ export default Vue.extend({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		repost() {
 | 
			
		||||
		renote() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				repost: this.p
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		react() {
 | 
			
		||||
			(this as any).os.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
				post: this.p,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		menu() {
 | 
			
		||||
			(this as any).os.new(MkPostMenu, {
 | 
			
		||||
			(this as any).os.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				post: this.p,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
@@ -225,7 +225,7 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.mk-post-detail
 | 
			
		||||
.mk-note-detail
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	padding 0
 | 
			
		||||
@@ -267,7 +267,7 @@ export default Vue.extend({
 | 
			
		||||
		> *
 | 
			
		||||
			border-bottom 1px solid #eef0f2
 | 
			
		||||
 | 
			
		||||
	> .repost
 | 
			
		||||
	> .renote
 | 
			
		||||
		color #9dbb00
 | 
			
		||||
		background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
 | 
			
		||||
 | 
			
		||||
@@ -357,10 +357,10 @@ export default Vue.extend({
 | 
			
		||||
		> .body
 | 
			
		||||
			padding 8px 0
 | 
			
		||||
 | 
			
		||||
			> .repost
 | 
			
		||||
			> .renote
 | 
			
		||||
				margin 8px 0
 | 
			
		||||
 | 
			
		||||
				> .mk-post-preview
 | 
			
		||||
				> .mk-note-preview
 | 
			
		||||
					padding 16px
 | 
			
		||||
					border dashed 1px #c0dac6
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@
 | 
			
		||||
		</div>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div class="form">
 | 
			
		||||
		<mk-post-preview v-if="reply" :post="reply"/>
 | 
			
		||||
		<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea>
 | 
			
		||||
		<mk-note-preview v-if="reply" :note="reply"/>
 | 
			
		||||
		<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.note-placeholder%'"></textarea>
 | 
			
		||||
		<div class="attaches" v-show="files.length != 0">
 | 
			
		||||
			<x-draggable class="files" :list="files" :options="{ animation: 150 }">
 | 
			
		||||
				<div class="file" v-for="file in files" :key="file.id">
 | 
			
		||||
@@ -112,7 +112,7 @@ export default Vue.extend({
 | 
			
		||||
		post() {
 | 
			
		||||
			this.posting = true;
 | 
			
		||||
			const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== true;
 | 
			
		||||
			(this as any).api('posts/create', {
 | 
			
		||||
			(this as any).api('notes/create', {
 | 
			
		||||
				text: this.text == '' ? undefined : this.text,
 | 
			
		||||
				mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
 | 
			
		||||
				replyId: this.reply ? this.reply.id : undefined,
 | 
			
		||||
@@ -127,7 +127,7 @@ export default Vue.extend({
 | 
			
		||||
				} : null,
 | 
			
		||||
				viaMobile: viaMobile
 | 
			
		||||
			}).then(data => {
 | 
			
		||||
				this.$emit('post');
 | 
			
		||||
				this.$emit('note');
 | 
			
		||||
				this.$destroy();
 | 
			
		||||
			}).catch(err => {
 | 
			
		||||
				this.posting = false;
 | 
			
		||||
@@ -200,7 +200,7 @@ export default Vue.extend({
 | 
			
		||||
		max-width 500px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
 | 
			
		||||
		> .mk-post-preview
 | 
			
		||||
		> .mk-note-preview
 | 
			
		||||
			padding 16px
 | 
			
		||||
 | 
			
		||||
		> .attaches
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="post" :class="{ repost: isRepost }">
 | 
			
		||||
<div class="note" :class="{ renote: isRenote }">
 | 
			
		||||
	<div class="reply-to" v-if="p.reply">
 | 
			
		||||
		<x-sub :post="p.reply"/>
 | 
			
		||||
		<x-sub :note="p.reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="repost" v-if="isRepost">
 | 
			
		||||
	<div class="renote" v-if="isRenote">
 | 
			
		||||
		<p>
 | 
			
		||||
			<router-link class="avatar-anchor" :to="`/@${acct}`">
 | 
			
		||||
				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			%fa:retweet%
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span>
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span>
 | 
			
		||||
			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span>
 | 
			
		||||
			<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
 | 
			
		||||
		</p>
 | 
			
		||||
		<mk-time :time="post.createdAt"/>
 | 
			
		||||
		<mk-time :time="note.createdAt"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<article>
 | 
			
		||||
		<router-link class="avatar-anchor" :to="`/@${pAcct}`">
 | 
			
		||||
@@ -37,13 +37,13 @@
 | 
			
		||||
					<a class="reply" v-if="p.reply">
 | 
			
		||||
						%fa:reply%
 | 
			
		||||
					</a>
 | 
			
		||||
					<mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
					<a class="rp" v-if="p.repost != null">RP:</a>
 | 
			
		||||
					<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
 | 
			
		||||
					<a class="rp" v-if="p.renote != null">RP:</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="media" v-if="p.media.length > 0">
 | 
			
		||||
					<mk-media-list :media-list="p.media"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<mk-poll v-if="p.poll" :post="p" ref="pollViewer"/>
 | 
			
		||||
				<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
 | 
			
		||||
				<div class="tags" v-if="p.tags && p.tags.length > 0">
 | 
			
		||||
					<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
 | 
			
		||||
				</div>
 | 
			
		||||
@@ -51,17 +51,17 @@
 | 
			
		||||
				<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
 | 
			
		||||
				<div class="map" v-if="p.geo" ref="map"></div>
 | 
			
		||||
				<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
 | 
			
		||||
				<div class="repost" v-if="p.repost">
 | 
			
		||||
					<mk-post-preview :post="p.repost"/>
 | 
			
		||||
				<div class="renote" v-if="p.renote">
 | 
			
		||||
					<mk-note-preview :note="p.renote"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<footer>
 | 
			
		||||
				<mk-reactions-viewer :post="p" ref="reactionsViewer"/>
 | 
			
		||||
				<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
 | 
			
		||||
				<button @click="reply">
 | 
			
		||||
					%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button @click="repost" title="Repost">
 | 
			
		||||
					%fa:retweet%<p class="count" v-if="p.repostCount > 0">{{ p.repostCount }}</p>
 | 
			
		||||
				<button @click="renote" title="Renote">
 | 
			
		||||
					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
 | 
			
		||||
					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
 | 
			
		||||
@@ -81,16 +81,16 @@ import getAcct from '../../../../../acct/render';
 | 
			
		||||
import getUserName from '../../../../../renderers/get-user-name';
 | 
			
		||||
import parse from '../../../../../text/parse';
 | 
			
		||||
 | 
			
		||||
import MkPostMenu from '../../../common/views/components/post-menu.vue';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './post.sub.vue';
 | 
			
		||||
import XSub from './note.sub.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XSub
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
@@ -101,10 +101,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		acct(): string {
 | 
			
		||||
			return getAcct(this.post.user);
 | 
			
		||||
			return getAcct(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		name(): string {
 | 
			
		||||
			return getUserName(this.post.user);
 | 
			
		||||
			return getUserName(this.note.user);
 | 
			
		||||
		},
 | 
			
		||||
		pAcct(): string {
 | 
			
		||||
			return getAcct(this.p.user);
 | 
			
		||||
@@ -112,14 +112,14 @@ export default Vue.extend({
 | 
			
		||||
		pName(): string {
 | 
			
		||||
			return getUserName(this.p.user);
 | 
			
		||||
		},
 | 
			
		||||
		isRepost(): boolean {
 | 
			
		||||
			return (this.post.repost &&
 | 
			
		||||
				this.post.text == null &&
 | 
			
		||||
				this.post.mediaIds == null &&
 | 
			
		||||
				this.post.poll == null);
 | 
			
		||||
		isRenote(): boolean {
 | 
			
		||||
			return (this.note.renote &&
 | 
			
		||||
				this.note.text == null &&
 | 
			
		||||
				this.note.mediaIds == null &&
 | 
			
		||||
				this.note.poll == null);
 | 
			
		||||
		},
 | 
			
		||||
		p(): any {
 | 
			
		||||
			return this.isRepost ? this.post.repost : this.post;
 | 
			
		||||
			return this.isRenote ? this.note.renote : this.note;
 | 
			
		||||
		},
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
@@ -192,7 +192,7 @@ export default Vue.extend({
 | 
			
		||||
					type: 'capture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated);
 | 
			
		||||
				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		decapture(withHandler = false) {
 | 
			
		||||
@@ -201,18 +201,18 @@ export default Vue.extend({
 | 
			
		||||
					type: 'decapture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated);
 | 
			
		||||
				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onStreamConnected() {
 | 
			
		||||
			this.capture();
 | 
			
		||||
		},
 | 
			
		||||
		onStreamPostUpdated(data) {
 | 
			
		||||
			const post = data.post;
 | 
			
		||||
			if (post.id == this.post.id) {
 | 
			
		||||
				this.$emit('update:post', post);
 | 
			
		||||
			} else if (post.id == this.post.repostId) {
 | 
			
		||||
				this.post.repost = post;
 | 
			
		||||
		onStreamNoteUpdated(data) {
 | 
			
		||||
			const note = data.note;
 | 
			
		||||
			if (note.id == this.note.id) {
 | 
			
		||||
				this.$emit('update:note', note);
 | 
			
		||||
			} else if (note.id == this.note.renoteId) {
 | 
			
		||||
				this.note.renote = note;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		reply() {
 | 
			
		||||
@@ -220,22 +220,22 @@ export default Vue.extend({
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		repost() {
 | 
			
		||||
		renote() {
 | 
			
		||||
			(this as any).apis.post({
 | 
			
		||||
				repost: this.p
 | 
			
		||||
				renote: this.p
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		react() {
 | 
			
		||||
			(this as any).os.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
				post: this.p,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		menu() {
 | 
			
		||||
			(this as any).os.new(MkPostMenu, {
 | 
			
		||||
			(this as any).os.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				post: this.p,
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				compact: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
@@ -246,14 +246,14 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.post
 | 
			
		||||
.note
 | 
			
		||||
	font-size 12px
 | 
			
		||||
	border-bottom solid 1px #eaeaea
 | 
			
		||||
 | 
			
		||||
	&:first-child
 | 
			
		||||
		border-radius 8px 8px 0 0
 | 
			
		||||
 | 
			
		||||
		> .repost
 | 
			
		||||
		> .renote
 | 
			
		||||
			border-radius 8px 8px 0 0
 | 
			
		||||
 | 
			
		||||
	&:last-of-type
 | 
			
		||||
@@ -265,7 +265,7 @@ export default Vue.extend({
 | 
			
		||||
	@media (min-width 500px)
 | 
			
		||||
		font-size 16px
 | 
			
		||||
 | 
			
		||||
	> .repost
 | 
			
		||||
	> .renote
 | 
			
		||||
		color #9dbb00
 | 
			
		||||
		background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
 | 
			
		||||
 | 
			
		||||
@@ -309,7 +309,7 @@ export default Vue.extend({
 | 
			
		||||
	> .reply-to
 | 
			
		||||
		background rgba(0, 0, 0, 0.0125)
 | 
			
		||||
 | 
			
		||||
		> .mk-post-preview
 | 
			
		||||
		> .mk-note-preview
 | 
			
		||||
			background transparent
 | 
			
		||||
 | 
			
		||||
	> article
 | 
			
		||||
@@ -485,10 +485,10 @@ export default Vue.extend({
 | 
			
		||||
				> .mk-poll
 | 
			
		||||
					font-size 80%
 | 
			
		||||
 | 
			
		||||
				> .repost
 | 
			
		||||
				> .renote
 | 
			
		||||
					margin 8px 0
 | 
			
		||||
 | 
			
		||||
					> .mk-post-preview
 | 
			
		||||
					> .mk-note-preview
 | 
			
		||||
						padding 16px
 | 
			
		||||
						border dashed 1px #c0dac6
 | 
			
		||||
						border-radius 8px
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/client/app/mobile/views/components/sub-note-content.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/client/app/mobile/views/components/sub-note-content.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-sub-note-content">
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<a class="reply" v-if="note.replyId">%fa:reply%</a>
 | 
			
		||||
		<mk-note-html v-if="note.text" :text="note.text" :i="os.i"/>
 | 
			
		||||
		<a class="rp" v-if="note.renoteId">RP: ...</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<details v-if="note.media.length > 0">
 | 
			
		||||
		<summary>({{ note.media.length }}個のメディア)</summary>
 | 
			
		||||
		<mk-media-list :media-list="note.media"/>
 | 
			
		||||
	</details>
 | 
			
		||||
	<details v-if="note.poll">
 | 
			
		||||
		<summary>%i18n:mobile.tags.mk-sub-note-content.poll%</summary>
 | 
			
		||||
		<mk-poll :note="note"/>
 | 
			
		||||
	</details>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['note']
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-sub-note-content
 | 
			
		||||
	overflow-wrap break-word
 | 
			
		||||
 | 
			
		||||
	> .body
 | 
			
		||||
		> .reply
 | 
			
		||||
			margin-right 6px
 | 
			
		||||
			color #717171
 | 
			
		||||
 | 
			
		||||
		> .rp
 | 
			
		||||
			margin-left 4px
 | 
			
		||||
			font-style oblique
 | 
			
		||||
			color #a0bf46
 | 
			
		||||
 | 
			
		||||
	mk-poll
 | 
			
		||||
		font-size 80%
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-sub-post-content">
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<a class="reply" v-if="post.replyId">%fa:reply%</a>
 | 
			
		||||
		<mk-post-html v-if="post.text" :text="post.text" :i="os.i"/>
 | 
			
		||||
		<a class="rp" v-if="post.repostId">RP: ...</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<details v-if="post.media.length > 0">
 | 
			
		||||
		<summary>({{ post.media.length }}個のメディア)</summary>
 | 
			
		||||
		<mk-media-list :media-list="post.media"/>
 | 
			
		||||
	</details>
 | 
			
		||||
	<details v-if="post.poll">
 | 
			
		||||
		<summary>%i18n:mobile.tags.mk-sub-post-content.poll%</summary>
 | 
			
		||||
		<mk-poll :post="post"/>
 | 
			
		||||
	</details>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post']
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-sub-post-content
 | 
			
		||||
	overflow-wrap break-word
 | 
			
		||||
 | 
			
		||||
	> .body
 | 
			
		||||
		> .reply
 | 
			
		||||
			margin-right 6px
 | 
			
		||||
			color #717171
 | 
			
		||||
 | 
			
		||||
		> .rp
 | 
			
		||||
			margin-left 4px
 | 
			
		||||
			font-style oblique
 | 
			
		||||
			color #a0bf46
 | 
			
		||||
 | 
			
		||||
	mk-poll
 | 
			
		||||
		font-size 80%
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-timeline">
 | 
			
		||||
	<mk-friends-maker v-if="alone"/>
 | 
			
		||||
	<mk-posts :posts="posts">
 | 
			
		||||
	<mk-notes :notes="notes">
 | 
			
		||||
		<div class="init" v-if="fetching">
 | 
			
		||||
			%fa:spinner .pulse%%i18n:common.loading%
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="empty" v-if="!fetching && posts.length == 0">
 | 
			
		||||
		<div class="empty" v-if="!fetching && notes.length == 0">
 | 
			
		||||
			%fa:R comments%
 | 
			
		||||
			%i18n:mobile.tags.mk-home-timeline.empty-timeline%
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
			<span v-if="!moreFetching">%i18n:mobile.tags.mk-timeline.load-more%</span>
 | 
			
		||||
			<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
 | 
			
		||||
		</button>
 | 
			
		||||
	</mk-posts>
 | 
			
		||||
	</mk-notes>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -33,7 +33,7 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			moreFetching: false,
 | 
			
		||||
			posts: [],
 | 
			
		||||
			notes: [],
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
@@ -48,14 +48,14 @@ export default Vue.extend({
 | 
			
		||||
		this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('post', this.onPost);
 | 
			
		||||
		this.connection.on('note', this.onNote);
 | 
			
		||||
		this.connection.on('follow', this.onChangeFollowing);
 | 
			
		||||
		this.connection.on('unfollow', this.onChangeFollowing);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('post', this.onPost);
 | 
			
		||||
		this.connection.off('note', this.onNote);
 | 
			
		||||
		this.connection.off('follow', this.onChangeFollowing);
 | 
			
		||||
		this.connection.off('unfollow', this.onChangeFollowing);
 | 
			
		||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
@@ -63,15 +63,15 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch(cb?) {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
			(this as any).api('posts/timeline', {
 | 
			
		||||
			(this as any).api('notes/timeline', {
 | 
			
		||||
				limit: limit + 1,
 | 
			
		||||
				untilDate: this.date ? (this.date as any).getTime() : undefined
 | 
			
		||||
			}).then(posts => {
 | 
			
		||||
				if (posts.length == limit + 1) {
 | 
			
		||||
					posts.pop();
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				if (notes.length == limit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
					this.existMore = true;
 | 
			
		||||
				}
 | 
			
		||||
				this.posts = posts;
 | 
			
		||||
				this.notes = notes;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
				this.$emit('loaded');
 | 
			
		||||
				if (cb) cb();
 | 
			
		||||
@@ -79,22 +79,22 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
		more() {
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
			(this as any).api('posts/timeline', {
 | 
			
		||||
			(this as any).api('notes/timeline', {
 | 
			
		||||
				limit: limit + 1,
 | 
			
		||||
				untilId: this.posts[this.posts.length - 1].id
 | 
			
		||||
			}).then(posts => {
 | 
			
		||||
				if (posts.length == limit + 1) {
 | 
			
		||||
					posts.pop();
 | 
			
		||||
				untilId: this.notes[this.notes.length - 1].id
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				if (notes.length == limit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
					this.existMore = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				this.posts = this.posts.concat(posts);
 | 
			
		||||
				this.notes = this.notes.concat(notes);
 | 
			
		||||
				this.moreFetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onPost(post) {
 | 
			
		||||
			this.posts.unshift(post);
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			this.notes.unshift(note);
 | 
			
		||||
		},
 | 
			
		||||
		onChangeFollowing() {
 | 
			
		||||
			this.fetch();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-user-timeline">
 | 
			
		||||
	<mk-posts :posts="posts">
 | 
			
		||||
	<mk-notes :notes="notes">
 | 
			
		||||
		<div class="init" v-if="fetching">
 | 
			
		||||
			%fa:spinner .pulse%%i18n:common.loading%
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="empty" v-if="!fetching && posts.length == 0">
 | 
			
		||||
		<div class="empty" v-if="!fetching && notes.length == 0">
 | 
			
		||||
			%fa:R comments%
 | 
			
		||||
			{{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-posts-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-posts%' }}
 | 
			
		||||
			{{ withMedia ? '%i18n:mobile.tags.mk-user-timeline.no-notes-with-media%' : '%i18n:mobile.tags.mk-user-timeline.no-notes%' }}
 | 
			
		||||
		</div>
 | 
			
		||||
		<button v-if="!fetching && existMore" @click="more" :disabled="moreFetching" slot="tail">
 | 
			
		||||
			<span v-if="!moreFetching">%i18n:mobile.tags.mk-user-timeline.load-more%</span>
 | 
			
		||||
			<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
 | 
			
		||||
		</button>
 | 
			
		||||
	</mk-posts>
 | 
			
		||||
	</mk-notes>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -26,22 +26,22 @@ export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			posts: [],
 | 
			
		||||
			notes: [],
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			moreFetching: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).api('users/posts', {
 | 
			
		||||
		(this as any).api('users/notes', {
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			withMedia: this.withMedia,
 | 
			
		||||
			limit: limit + 1
 | 
			
		||||
		}).then(posts => {
 | 
			
		||||
			if (posts.length == limit + 1) {
 | 
			
		||||
				posts.pop();
 | 
			
		||||
		}).then(notes => {
 | 
			
		||||
			if (notes.length == limit + 1) {
 | 
			
		||||
				notes.pop();
 | 
			
		||||
				this.existMore = true;
 | 
			
		||||
			}
 | 
			
		||||
			this.posts = posts;
 | 
			
		||||
			this.notes = notes;
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
			this.$emit('loaded');
 | 
			
		||||
		});
 | 
			
		||||
@@ -49,19 +49,19 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		more() {
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
			(this as any).api('users/posts', {
 | 
			
		||||
			(this as any).api('users/notes', {
 | 
			
		||||
				userId: this.user.id,
 | 
			
		||||
				withMedia: this.withMedia,
 | 
			
		||||
				limit: limit + 1,
 | 
			
		||||
				untilId: this.posts[this.posts.length - 1].id
 | 
			
		||||
			}).then(posts => {
 | 
			
		||||
				if (posts.length == limit + 1) {
 | 
			
		||||
					posts.pop();
 | 
			
		||||
				untilId: this.notes[this.notes.length - 1].id
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				if (notes.length == limit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
					this.existMore = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				this.posts = this.posts.concat(posts);
 | 
			
		||||
				this.notes = this.notes.concat(notes);
 | 
			
		||||
				this.moreFetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ import Vue from 'vue';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import Progress from '../../../common/scripts/loading';
 | 
			
		||||
import getPostSummary from '../../../../../renderers/get-post-summary';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -124,14 +124,14 @@ export default Vue.extend({
 | 
			
		||||
		this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('post', this.onStreamPost);
 | 
			
		||||
		this.connection.on('note', this.onStreamNote);
 | 
			
		||||
		this.connection.on('mobile_home_updated', this.onHomeUpdated);
 | 
			
		||||
		document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 | 
			
		||||
 | 
			
		||||
		Progress.start();
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('post', this.onStreamPost);
 | 
			
		||||
		this.connection.off('note', this.onStreamNote);
 | 
			
		||||
		this.connection.off('mobile_home_updated', this.onHomeUpdated);
 | 
			
		||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
		document.removeEventListener('visibilitychange', this.onVisibilitychange);
 | 
			
		||||
@@ -143,10 +143,10 @@ export default Vue.extend({
 | 
			
		||||
		onLoaded() {
 | 
			
		||||
			Progress.done();
 | 
			
		||||
		},
 | 
			
		||||
		onStreamPost(post) {
 | 
			
		||||
			if (document.hidden && post.userId !== (this as any).os.i.id) {
 | 
			
		||||
		onStreamNote(note) {
 | 
			
		||||
			if (document.hidden && note.userId !== (this as any).os.i.id) {
 | 
			
		||||
				this.unreadCount++;
 | 
			
		||||
				document.title = `(${this.unreadCount}) ${getPostSummary(post)}`;
 | 
			
		||||
				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onVisibilitychange() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-post-page.title%</span>
 | 
			
		||||
	<span slot="header">%fa:R sticky-note%%i18n:mobile.tags.mk-note-page.title%</span>
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<a v-if="post.next" :href="post.next">%fa:angle-up%%i18n:mobile.tags.mk-post-page.next%</a>
 | 
			
		||||
		<a v-if="note.next" :href="note.next">%fa:angle-up%%i18n:mobile.tags.mk-note-page.next%</a>
 | 
			
		||||
		<div>
 | 
			
		||||
			<mk-post-detail :post="post"/>
 | 
			
		||||
			<mk-note-detail :note="note"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<a v-if="post.prev" :href="post.prev">%fa:angle-down%%i18n:mobile.tags.mk-post-page.prev%</a>
 | 
			
		||||
		<a v-if="note.prev" :href="note.prev">%fa:angle-down%%i18n:mobile.tags.mk-note-page.prev%</a>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -19,7 +19,7 @@ export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			post: null
 | 
			
		||||
			note: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
@@ -37,10 +37,10 @@ export default Vue.extend({
 | 
			
		||||
			Progress.start();
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this as any).api('posts/show', {
 | 
			
		||||
				postId: this.$route.params.post
 | 
			
		||||
			}).then(post => {
 | 
			
		||||
				this.post = post;
 | 
			
		||||
			(this as any).api('notes/show', {
 | 
			
		||||
				noteId: this.$route.params.note
 | 
			
		||||
			}).then(note => {
 | 
			
		||||
				this.note = note;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				Progress.done();
 | 
			
		||||
@@ -2,13 +2,13 @@
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:search% {{ q }}</span>
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<mk-posts :class="$style.posts" :posts="posts">
 | 
			
		||||
			<span v-if="posts.length == 0">{{ '%i18n:mobile.tags.mk-search-posts.empty%'.replace('{}', q) }}</span>
 | 
			
		||||
		<mk-notes :class="$style.notes" :notes="notes">
 | 
			
		||||
			<span v-if="notes.length == 0">{{ '%i18n:mobile.tags.mk-search-notes.empty%'.replace('{}', q) }}</span>
 | 
			
		||||
			<button v-if="existMore" @click="more" :disabled="fetching" slot="tail">
 | 
			
		||||
				<span v-if="!fetching">%i18n:mobile.tags.mk-timeline.load-more%</span>
 | 
			
		||||
				<span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span>
 | 
			
		||||
			</button>
 | 
			
		||||
		</mk-posts>
 | 
			
		||||
		</mk-notes>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -25,7 +25,7 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			posts: [],
 | 
			
		||||
			notes: [],
 | 
			
		||||
			offset: 0
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
@@ -48,30 +48,30 @@ export default Vue.extend({
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
			Progress.start();
 | 
			
		||||
 | 
			
		||||
			(this as any).api('posts/search', Object.assign({
 | 
			
		||||
			(this as any).api('notes/search', Object.assign({
 | 
			
		||||
				limit: limit + 1
 | 
			
		||||
			}, parse(this.q))).then(posts => {
 | 
			
		||||
				if (posts.length == limit + 1) {
 | 
			
		||||
					posts.pop();
 | 
			
		||||
			}, parse(this.q))).then(notes => {
 | 
			
		||||
				if (notes.length == limit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
					this.existMore = true;
 | 
			
		||||
				}
 | 
			
		||||
				this.posts = posts;
 | 
			
		||||
				this.notes = notes;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
				Progress.done();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		more() {
 | 
			
		||||
			this.offset += limit;
 | 
			
		||||
			(this as any).api('posts/search', Object.assign({
 | 
			
		||||
			(this as any).api('notes/search', Object.assign({
 | 
			
		||||
				limit: limit + 1,
 | 
			
		||||
				offset: this.offset
 | 
			
		||||
			}, parse(this.q))).then(posts => {
 | 
			
		||||
				if (posts.length == limit + 1) {
 | 
			
		||||
					posts.pop();
 | 
			
		||||
			}, parse(this.q))).then(notes => {
 | 
			
		||||
				if (notes.length == limit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				this.posts = this.posts.concat(posts);
 | 
			
		||||
				this.notes = this.notes.concat(notes);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -79,7 +79,7 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.posts
 | 
			
		||||
.notes
 | 
			
		||||
	margin 8px auto
 | 
			
		||||
	max-width 500px
 | 
			
		||||
	width calc(100% - 16px)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="status">
 | 
			
		||||
					<a>
 | 
			
		||||
						<b>{{ user.postsCount | number }}</b>
 | 
			
		||||
						<i>%i18n:mobile.tags.mk-user.posts%</i>
 | 
			
		||||
						<b>{{ user.notesCount | number }}</b>
 | 
			
		||||
						<i>%i18n:mobile.tags.mk-user.notes%</i>
 | 
			
		||||
					</a>
 | 
			
		||||
					<a :href="`@${acct}/following`">
 | 
			
		||||
						<b>{{ user.followingCount | number }}</b>
 | 
			
		||||
@@ -44,13 +44,13 @@
 | 
			
		||||
		<nav>
 | 
			
		||||
			<div class="nav-container">
 | 
			
		||||
				<a :data-is-active=" page == 'home' " @click="page = 'home'">%i18n:mobile.tags.mk-user.overview%</a>
 | 
			
		||||
				<a :data-is-active=" page == 'posts' " @click="page = 'posts'">%i18n:mobile.tags.mk-user.timeline%</a>
 | 
			
		||||
				<a :data-is-active=" page == 'notes' " @click="page = 'notes'">%i18n:mobile.tags.mk-user.timeline%</a>
 | 
			
		||||
				<a :data-is-active=" page == 'media' " @click="page = 'media'">%i18n:mobile.tags.mk-user.media%</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</nav>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<x-home v-if="page == 'home'" :user="user"/>
 | 
			
		||||
			<mk-user-timeline v-if="page == 'posts'" :user="user"/>
 | 
			
		||||
			<mk-user-timeline v-if="page == 'notes'" :user="user"/>
 | 
			
		||||
			<mk-user-timeline v-if="page == 'media'" :user="user" with-media/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="root posts">
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p>
 | 
			
		||||
	<div v-if="!fetching && posts.length > 0">
 | 
			
		||||
		<mk-post-card v-for="post in posts" :key="post.id" :post="post"/>
 | 
			
		||||
<div class="root notes">
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-notes.loading%<mk-ellipsis/></p>
 | 
			
		||||
	<div v-if="!fetching && notes.length > 0">
 | 
			
		||||
		<mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="empty" v-if="!fetching && posts.length == 0">%i18n:mobile.tags.mk-user-overview-posts.no-posts%</p>
 | 
			
		||||
	<p class="empty" v-if="!fetching && notes.length == 0">%i18n:mobile.tags.mk-user-overview-notes.no-notes%</p>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -15,14 +15,14 @@ export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			posts: []
 | 
			
		||||
			notes: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).api('users/posts', {
 | 
			
		||||
		(this as any).api('users/notes', {
 | 
			
		||||
			userId: this.user.id
 | 
			
		||||
		}).then(posts => {
 | 
			
		||||
			this.posts = posts;
 | 
			
		||||
		}).then(notes => {
 | 
			
		||||
			this.notes = notes;
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
@@ -30,7 +30,7 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.root.posts
 | 
			
		||||
.root.notes
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		overflow-x scroll
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
		<a v-for="image in images"
 | 
			
		||||
			class="img"
 | 
			
		||||
			:style="`background-image: url(${image.media.url}?thumbnail&size=256)`"
 | 
			
		||||
			:href="`/@${getAcct(image.post.user)}/${image.post.id}`"
 | 
			
		||||
			:href="`/@${getAcct(image.note.user)}/${image.note.id}`"
 | 
			
		||||
		></a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="empty" v-if="!fetching && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p>
 | 
			
		||||
@@ -28,15 +28,15 @@ export default Vue.extend({
 | 
			
		||||
		getAcct
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).api('users/posts', {
 | 
			
		||||
		(this as any).api('users/notes', {
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			withMedia: true,
 | 
			
		||||
			limit: 6
 | 
			
		||||
		}).then(posts => {
 | 
			
		||||
			posts.forEach(post => {
 | 
			
		||||
				post.media.forEach(media => {
 | 
			
		||||
		}).then(notes => {
 | 
			
		||||
			notes.forEach(note => {
 | 
			
		||||
				note.media.forEach(media => {
 | 
			
		||||
					if (this.images.length < 9) this.images.push({
 | 
			
		||||
						post,
 | 
			
		||||
						note,
 | 
			
		||||
						media
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="root home">
 | 
			
		||||
	<mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/>
 | 
			
		||||
	<section class="recent-posts">
 | 
			
		||||
		<h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2>
 | 
			
		||||
	<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
 | 
			
		||||
	<section class="recent-notes">
 | 
			
		||||
		<h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-notes%</h2>
 | 
			
		||||
		<div>
 | 
			
		||||
			<x-posts :user="user"/>
 | 
			
		||||
			<x-notes :user="user"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</section>
 | 
			
		||||
	<section class="images">
 | 
			
		||||
@@ -37,14 +37,14 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XPosts from './home.posts.vue';
 | 
			
		||||
import XNotes from './home.notes.vue';
 | 
			
		||||
import XPhotos from './home.photos.vue';
 | 
			
		||||
import XFriends from './home.friends.vue';
 | 
			
		||||
import XFollowersYouKnow from './home.followers-you-know.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XPosts,
 | 
			
		||||
		XNotes,
 | 
			
		||||
		XPhotos,
 | 
			
		||||
		XFriends,
 | 
			
		||||
		XFollowersYouKnow
 | 
			
		||||
@@ -58,7 +58,7 @@ export default Vue.extend({
 | 
			
		||||
	max-width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
 | 
			
		||||
	> .mk-post-detail
 | 
			
		||||
	> .mk-note-detail
 | 
			
		||||
		margin 0 0 8px 0
 | 
			
		||||
 | 
			
		||||
	> section
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user