Refactor
This commit is contained in:
		| @@ -3,8 +3,8 @@ | ||||
| 	<ol class="users" ref="suggests" v-if="users.length > 0"> | ||||
| 		<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> | ||||
| 			<span class="name">{{ getUserName(user) }}</span> | ||||
| 			<span class="username">@{{ getAcct(user) }}</span> | ||||
| 			<span class="name">{{ user | userName }}</span> | ||||
| 			<span class="username">@{{ user | acct }}</span> | ||||
| 		</li> | ||||
| 	</ol> | ||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||
| @@ -21,17 +21,17 @@ | ||||
| import Vue from 'vue'; | ||||
| import * as emojilib from 'emojilib'; | ||||
| import contains from '../../../common/scripts/contains'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| const lib = Object.entries(emojilib.lib).filter((x: any) => { | ||||
| 	return x[1].category != 'flags'; | ||||
| }); | ||||
|  | ||||
| const emjdb = lib.map((x: any) => ({ | ||||
| 	emoji: x[1].char, | ||||
| 	name: x[0], | ||||
| 	alias: null | ||||
| })); | ||||
|  | ||||
| lib.forEach((x: any) => { | ||||
| 	if (x[1].keywords) { | ||||
| 		x[1].keywords.forEach(k => { | ||||
| @@ -43,6 +43,7 @@ lib.forEach((x: any) => { | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| emjdb.sort((a, b) => a.name.length - b.name.length); | ||||
|  | ||||
| export default Vue.extend({ | ||||
| @@ -107,8 +108,6 @@ export default Vue.extend({ | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		exec() { | ||||
| 			this.select = -1; | ||||
| 			if (this.$refs.suggests) { | ||||
|   | ||||
| @@ -14,8 +14,8 @@ | ||||
| 					tabindex="-1" | ||||
| 				> | ||||
| 					<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> | ||||
| 					<span class="name">{{ getUserName(user) }}</span> | ||||
| 					<span class="username">@{{ getAcct(user) }}</span> | ||||
| 					<span class="name">{{ user | userName }}</span> | ||||
| 					<span class="username">@{{ user | acct }}</span> | ||||
| 				</li> | ||||
| 			</ol> | ||||
| 		</div> | ||||
| @@ -33,8 +33,8 @@ | ||||
| 				<div> | ||||
| 					<img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/> | ||||
| 					<header> | ||||
| 						<span class="name">{{ getUserName(isMe(message) ? message.recipient : message.user) }}</span> | ||||
| 						<span class="username">@{{ getAcct(isMe(message) ? message.recipient : message.user) }}</span> | ||||
| 						<span class="name">{{ isMe(message) ? message.recipient : message.use | userName }}</span> | ||||
| 						<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span> | ||||
| 						<mk-time :time="message.createdAt"/> | ||||
| 					</header> | ||||
| 					<div class="body"> | ||||
| @@ -51,8 +51,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| @@ -94,8 +92,6 @@ export default Vue.extend({ | ||||
| 		(this as any).os.streams.messagingIndexStream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		isMe(message) { | ||||
| 			return message.userId == (this as any).os.i.id; | ||||
| 		}, | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="mk-welcome-timeline"> | ||||
| 	<div v-for="note in notes"> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id"> | ||||
| 		<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.user.id"> | ||||
| 			<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="body"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="`/@${getAcct(note.user)}`" v-user-preview="note.user.id">{{ getUserName(note.user) }}</router-link> | ||||
| 				<span class="username">@{{ getAcct(note.user) }}</span> | ||||
| 				<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> | ||||
| 				<span class="username">@{{ note.user | acct }}</span> | ||||
| 				<div class="info"> | ||||
| 					<router-link class="created-at" :to="`/@${getAcct(note.user)}/${note.id}`"> | ||||
| 						<mk-time :time="note.createdAt"/> | ||||
| @@ -24,8 +24,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -38,8 +36,6 @@ export default Vue.extend({ | ||||
| 		this.fetch(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 			(this as any).api('notes', { | ||||
|   | ||||
| @@ -1,2 +1,4 @@ | ||||
| require('./bytes'); | ||||
| require('./number'); | ||||
| require('./user'); | ||||
| require('./note'); | ||||
|   | ||||
							
								
								
									
										5
									
								
								src/client/app/common/views/filters/note.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/client/app/common/views/filters/note.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| Vue.filter('notePage', note => { | ||||
| 	return '/notes/' + note.id; | ||||
| }); | ||||
							
								
								
									
										15
									
								
								src/client/app/common/views/filters/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/client/app/common/views/filters/user.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| Vue.filter('acct', user => { | ||||
| 	return getAcct(user); | ||||
| }); | ||||
|  | ||||
| Vue.filter('userName', user => { | ||||
| 	return getUserName(user); | ||||
| }); | ||||
|  | ||||
| Vue.filter('userPage', user => { | ||||
| 	return '/@' + Vue.filter('acct')(user); | ||||
| }); | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <mk-window width="400px" height="550px" @closed="$destroy"> | ||||
| 	<span slot="header" :class="$style.header"> | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロワー | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロワー | ||||
| 	</span> | ||||
| 	<mk-followers :user="user"/> | ||||
| </mk-window> | ||||
| @@ -9,15 +9,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed { | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['user'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <mk-window width="400px" height="550px" @closed="$destroy"> | ||||
| 	<span slot="header" :class="$style.header"> | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロー | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロー | ||||
| 	</span> | ||||
| 	<mk-following :user="user"/> | ||||
| </mk-window> | ||||
| @@ -9,15 +9,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['user'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -3,12 +3,12 @@ | ||||
| 	<p class="title">気になるユーザーをフォロー:</p> | ||||
| 	<div class="users" v-if="!fetching && users.length > 0"> | ||||
| 		<div class="user" v-for="user in users" :key="user.id"> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${getAcct(user)}`"> | ||||
| 			<router-link class="avatar-anchor" :to="user | userPage"> | ||||
| 				<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="user.id"/> | ||||
| 			</router-link> | ||||
| 			<div class="body"> | ||||
| 				<router-link class="name" :to="`/@${getAcct(user)}`" v-user-preview="user.id">{{ getUserName(user) }}</router-link> | ||||
| 				<p class="username">@{{ getAcct(user) }}</p> | ||||
| 				<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> | ||||
| 				<p class="username">@{{ user | acct }}</p> | ||||
| 			</div> | ||||
| 			<mk-follow-button :user="user"/> | ||||
| 		</div> | ||||
| @@ -22,8 +22,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -38,8 +36,6 @@ export default Vue.extend({ | ||||
| 		this.fetch(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		fetch() { | ||||
| 			this.fetching = true; | ||||
| 			this.users = []; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy"> | ||||
| 	<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ name }}</span> | ||||
| 	<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ user | userName }}</span> | ||||
| 	<mk-messaging-room :user="user" :class="$style.content"/> | ||||
| </mk-window> | ||||
| </template> | ||||
| @@ -9,14 +9,10 @@ | ||||
| import Vue from 'vue'; | ||||
| import { url } from '../../../config'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		name(): string { | ||||
| 			return getUserName(this.user); | ||||
| 		}, | ||||
| 		popout(): string { | ||||
| 			return `${url}/i/messaging/${getAcct(this.user)}`; | ||||
| 		} | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<div class="left"> | ||||
| 				<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> | ||||
| 				<span class="username">@{{ acct }}</span> | ||||
| 				<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 				<span class="username">@{{ note.user | acct }}</span> | ||||
| 			</div> | ||||
| 			<div class="right"> | ||||
| 				<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 				<router-link class="time" :to="note | notePage"> | ||||
| 					<mk-time :time="note.createdAt"/> | ||||
| 				</router-link> | ||||
| 			</div> | ||||
| @@ -28,18 +28,10 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
|   | ||||
| @@ -18,22 +18,22 @@ | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId"> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<router-link class="name" :href="`/@${acct}`">{{ name }}</router-link> | ||||
| 			<router-link class="name" :href="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${pAcct}`"> | ||||
| 		<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> | ||||
| 		</router-link> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ pAcct }}</span> | ||||
| 			<router-link class="time" :to="`/@${pAcct}/${p.id}`"> | ||||
| 			<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ p.user | acct }}</span> | ||||
| 			<router-link class="time" :to="p | notePage"> | ||||
| 				<mk-time :time="p.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -78,8 +78,6 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import parse from '../../../../../text/parse'; | ||||
|  | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| @@ -131,18 +129,6 @@ export default Vue.extend({ | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
| 		}, | ||||
| 		pName(): string { | ||||
| 			return getUserName(this.p.user); | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="mk-note-preview" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 			<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -21,18 +21,10 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="note.userId">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="created-at" :to="`/@${acct}/${note.id}`"> | ||||
| 			<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="created-at" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -21,18 +21,10 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name(): string { | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
|   | ||||
| @@ -5,29 +5,29 @@ | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.userId"> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<a class="name" :href="`/@${getAcct(note.user)}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a> | ||||
| 			<a class="name" :href="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</a> | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 		</p> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(p.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> | ||||
| 		</router-link> | ||||
| 		<div class="main"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="`/@${getAcct(p.user)}`" v-user-preview="p.user.id">{{ getUserName(p.user) }}</router-link> | ||||
| 				<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> | ||||
| 				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span> | ||||
| 				<span class="username">@{{ getAcct(p.user) }}</span> | ||||
| 				<span class="username">@{{ p.user | acct }}</span> | ||||
| 				<div class="info"> | ||||
| 					<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> | ||||
| 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> | ||||
| 					<router-link class="created-at" :to="url"> | ||||
| 					<router-link class="created-at" :to="p | notePage"> | ||||
| 						<mk-time :time="p.createdAt"/> | ||||
| 					</router-link> | ||||
| 				</div> | ||||
| @@ -85,8 +85,6 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import parse from '../../../../../text/parse'; | ||||
|  | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| @@ -117,9 +115,7 @@ export default Vue.extend({ | ||||
| 		return { | ||||
| 			isDetailOpened: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			getAcct, | ||||
| 			getUserName | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| @@ -144,9 +140,6 @@ export default Vue.extend({ | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		url(): string { | ||||
| 			return `/@${this.acct}/${this.p.id}`; | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
|   | ||||
| @@ -5,13 +5,13 @@ | ||||
| 			<div class="notification" :class="notification.type" :key="notification.id"> | ||||
| 				<mk-time :time="notification.createdAt"/> | ||||
| 				<template v-if="notification.type == 'reaction'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> | ||||
| 						<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p> | ||||
| 							<mk-reaction-icon :reaction="notification.reaction"/> | ||||
| 							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> | ||||
| 							<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| @@ -19,12 +19,12 @@ | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'renote'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:retweet% | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 							<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% | ||||
| @@ -32,54 +32,54 @@ | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'quote'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:quote-left% | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 							<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'follow'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> | ||||
| 						<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:user-plus% | ||||
| 							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> | ||||
| 							<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'reply'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:reply% | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 							<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 						<router-link class="note-preview" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</router-link> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'mention'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | ||||
| 						<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:at% | ||||
| 							<router-link :to="`/@${getAcct(notification.note.user)}`" v-user-preview="notification.note.userId">{{ getUserName(notification.note.user) }}</router-link> | ||||
| 							<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> | ||||
| 						</p> | ||||
| 						<a class="note-preview" :href="`/@${getAcct(notification.note.user)}/${notification.note.id}`">{{ getNoteSummary(notification.note) }}</a> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 				<template v-if="notification.type == 'poll_vote'"> | ||||
| 					<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id"> | ||||
| 					<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> | ||||
| 						<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> | ||||
| 					</router-link> | ||||
| 					<div class="text"> | ||||
| 						<p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p> | ||||
| 						<p>%fa:chart-pie%<a :href="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</a></p> | ||||
| 						<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 							%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| 						</router-link> | ||||
| @@ -102,9 +102,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -154,8 +152,6 @@ export default Vue.extend({ | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		fetchMoreNotifications() { | ||||
| 			this.fetchingMoreNotifications = true; | ||||
|  | ||||
|   | ||||
							
								
								
									
										122
									
								
								src/client/app/desktop/views/components/post-detail.sub.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/client/app/desktop/views/components/post-detail.sub.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<div class="left"> | ||||
| 				<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 				<span class="username">@{{ note.user | acct }}</span> | ||||
| 			</div> | ||||
| 			<div class="right"> | ||||
| 				<router-link class="time" :to="note | notePage"> | ||||
| 					<mk-time :time="note.createdAt"/> | ||||
| 				</router-link> | ||||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-note-html v-if="note.text" :text="note.text" :i="os.i" :class="$style.text"/> | ||||
| 			<div class="media" v-if="note.media > 0"> | ||||
| 				<mk-media-list :media-list="note.media"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .sub | ||||
| 	margin 0 | ||||
| 	padding 20px 32px | ||||
| 	background #fdfdfd | ||||
|  | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
|  | ||||
| 	&:hover | ||||
| 		> .main > footer > button | ||||
| 			color #888 | ||||
|  | ||||
| 	> .avatar-anchor | ||||
| 		display block | ||||
| 		float left | ||||
| 		margin 0 16px 0 0 | ||||
|  | ||||
| 		> .avatar | ||||
| 			display block | ||||
| 			width 44px | ||||
| 			height 44px | ||||
| 			margin 0 | ||||
| 			border-radius 4px | ||||
| 			vertical-align bottom | ||||
|  | ||||
| 	> .main | ||||
| 		float left | ||||
| 		width calc(100% - 60px) | ||||
|  | ||||
| 		> header | ||||
| 			margin-bottom 4px | ||||
| 			white-space nowrap | ||||
|  | ||||
| 			&:after | ||||
| 				content "" | ||||
| 				display block | ||||
| 				clear both | ||||
|  | ||||
| 			> .left | ||||
| 				float left | ||||
|  | ||||
| 				> .name | ||||
| 					display inline | ||||
| 					margin 0 | ||||
| 					padding 0 | ||||
| 					color #777 | ||||
| 					font-size 1em | ||||
| 					font-weight 700 | ||||
| 					text-align left | ||||
| 					text-decoration none | ||||
|  | ||||
| 					&:hover | ||||
| 						text-decoration underline | ||||
|  | ||||
| 				> .username | ||||
| 					text-align left | ||||
| 					margin 0 0 0 8px | ||||
| 					color #ccc | ||||
|  | ||||
| 			> .right | ||||
| 				float right | ||||
|  | ||||
| 				> .time | ||||
| 					font-size 0.9em | ||||
| 					color #c0c0c0 | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .text | ||||
| 	cursor default | ||||
| 	display block | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	overflow-wrap break-word | ||||
| 	font-size 1em | ||||
| 	color #717171 | ||||
| </style> | ||||
							
								
								
									
										434
									
								
								src/client/app/desktop/views/components/post-detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								src/client/app/desktop/views/components/post-detail.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,434 @@ | ||||
| <template> | ||||
| <div class="mk-note-detail" :title="title"> | ||||
| 	<button | ||||
| 		class="read-more" | ||||
| 		v-if="p.reply && p.reply.replyId && context == null" | ||||
| 		title="会話をもっと読み込む" | ||||
| 		@click="fetchContext" | ||||
| 		:disabled="contextFetching" | ||||
| 	> | ||||
| 		<template v-if="!contextFetching">%fa:ellipsis-v%</template> | ||||
| 		<template v-if="contextFetching">%fa:spinner .pulse%</template> | ||||
| 	</button> | ||||
| 	<div class="context"> | ||||
| 		<x-sub v-for="note in context" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<router-link class="name" :href="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> | ||||
| 		</router-link> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ p.user | acct }}</span> | ||||
| 			<router-link class="time" :to="p | notePage"> | ||||
| 				<mk-time :time="p.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-note-html :class="$style.text" v-if="p.text" :text="p.text" :i="os.i"/> | ||||
| 			<div class="media" v-if="p.media.length > 0"> | ||||
| 				<mk-media-list :media-list="p.media"/> | ||||
| 			</div> | ||||
| 			<mk-poll v-if="p.poll" :note="p"/> | ||||
| 			<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| 			<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 				<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 			</div> | ||||
| 			<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 			<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 			<div class="renote" v-if="p.renote"> | ||||
| 				<mk-note-preview :note="p.renote"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<footer> | ||||
| 			<mk-reactions-viewer :note="p"/> | ||||
| 			<button @click="reply" title="返信"> | ||||
| 				%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 			</button> | ||||
| 			<button @click="renote" title="Renote"> | ||||
| 				%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 			</button> | ||||
| 			<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="リアクション"> | ||||
| 				%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
| 			</button> | ||||
| 			<button @click="menu" ref="menuButton"> | ||||
| 				%fa:ellipsis-h% | ||||
| 			</button> | ||||
| 		</footer> | ||||
| 	</article> | ||||
| 	<div class="replies" v-if="!compact"> | ||||
| 		<x-sub v-for="note in replies" :key="note.id" :note="note"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import parse from '../../../../../text/parse'; | ||||
|  | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRenoteFormWindow from './renote-form-window.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './note-detail.sub.vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		note: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		compact: { | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			context: [], | ||||
| 			contextFetching: false, | ||||
| 			replies: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
| 				? Object.keys(this.p.reactionCounts) | ||||
| 					.map(key => this.p.reactionCounts[key]) | ||||
| 					.reduce((a, b) => a + b) | ||||
| 				: 0; | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		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.clientSettings.showMaps : true; | ||||
| 			if (shouldShowMap) { | ||||
| 				(this as any).os.getGoogleMaps().then(maps => { | ||||
| 					const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); | ||||
| 					const map = new maps.Map(this.$refs.map, { | ||||
| 						center: uluru, | ||||
| 						zoom: 15 | ||||
| 					}); | ||||
| 					new maps.Marker({ | ||||
| 						position: uluru, | ||||
| 						map: map | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		fetchContext() { | ||||
| 			this.contextFetching = true; | ||||
|  | ||||
| 			// Fetch context | ||||
| 			(this as any).api('notes/context', { | ||||
| 				noteId: this.p.replyId | ||||
| 			}).then(context => { | ||||
| 				this.contextFetching = false; | ||||
| 				this.context = context.reverse(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		reply() { | ||||
| 			(this as any).os.new(MkPostFormWindow, { | ||||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		renote() { | ||||
| 			(this as any).os.new(MkRenoteFormWindow, { | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .mk-note-detail | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	overflow hidden | ||||
| 	text-align left | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.1) | ||||
| 	border-radius 8px | ||||
|  | ||||
| 	> .read-more | ||||
| 		display block | ||||
| 		margin 0 | ||||
| 		padding 10px 0 | ||||
| 		width 100% | ||||
| 		font-size 1em | ||||
| 		text-align center | ||||
| 		color #999 | ||||
| 		cursor pointer | ||||
| 		background #fafafa | ||||
| 		outline none | ||||
| 		border none | ||||
| 		border-bottom solid 1px #eef0f2 | ||||
| 		border-radius 6px 6px 0 0 | ||||
|  | ||||
| 		&:hover | ||||
| 			background #f6f6f6 | ||||
|  | ||||
| 		&:active | ||||
| 			background #f0f0f0 | ||||
|  | ||||
| 		&:disabled | ||||
| 			color #ccc | ||||
|  | ||||
| 	> .context | ||||
| 		> * | ||||
| 			border-bottom 1px solid #eef0f2 | ||||
|  | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
|  | ||||
| 		> p | ||||
| 			margin 0 | ||||
| 			padding 16px 32px | ||||
|  | ||||
| 			.avatar-anchor | ||||
| 				display inline-block | ||||
|  | ||||
| 				.avatar | ||||
| 					vertical-align bottom | ||||
| 					min-width 28px | ||||
| 					min-height 28px | ||||
| 					max-width 28px | ||||
| 					max-height 28px | ||||
| 					margin 0 8px 0 0 | ||||
| 					border-radius 6px | ||||
|  | ||||
| 			[data-fa] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 			.name | ||||
| 				font-weight bold | ||||
|  | ||||
| 		& + article | ||||
| 			padding-top 8px | ||||
|  | ||||
| 	> .reply-to | ||||
| 		border-bottom 1px solid #eef0f2 | ||||
|  | ||||
| 	> article | ||||
| 		padding 28px 32px 18px 32px | ||||
|  | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			display block | ||||
| 			clear both | ||||
|  | ||||
| 		&:hover | ||||
| 			> .main > footer > button | ||||
| 				color #888 | ||||
|  | ||||
| 		> .avatar-anchor | ||||
| 			display block | ||||
| 			width 60px | ||||
| 			height 60px | ||||
|  | ||||
| 			> .avatar | ||||
| 				display block | ||||
| 				width 60px | ||||
| 				height 60px | ||||
| 				margin 0 | ||||
| 				border-radius 8px | ||||
| 				vertical-align bottom | ||||
|  | ||||
| 		> header | ||||
| 			position absolute | ||||
| 			top 28px | ||||
| 			left 108px | ||||
| 			width calc(100% - 108px) | ||||
|  | ||||
| 			> .name | ||||
| 				display inline-block | ||||
| 				margin 0 | ||||
| 				line-height 24px | ||||
| 				color #777 | ||||
| 				font-size 18px | ||||
| 				font-weight 700 | ||||
| 				text-align left | ||||
| 				text-decoration none | ||||
|  | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
|  | ||||
| 			> .username | ||||
| 				display block | ||||
| 				text-align left | ||||
| 				margin 0 | ||||
| 				color #ccc | ||||
|  | ||||
| 			> .time | ||||
| 				position absolute | ||||
| 				top 0 | ||||
| 				right 32px | ||||
| 				font-size 1em | ||||
| 				color #c0c0c0 | ||||
|  | ||||
| 		> .body | ||||
| 			padding 8px 0 | ||||
|  | ||||
| 			> .renote | ||||
| 				margin 8px 0 | ||||
|  | ||||
| 				> .mk-note-preview | ||||
| 					padding 16px | ||||
| 					border dashed 1px #c0dac6 | ||||
| 					border-radius 8px | ||||
|  | ||||
| 			> .location | ||||
| 				margin 4px 0 | ||||
| 				font-size 12px | ||||
| 				color #ccc | ||||
|  | ||||
| 			> .map | ||||
| 				width 100% | ||||
| 				height 300px | ||||
|  | ||||
| 				&:empty | ||||
| 					display none | ||||
|  | ||||
| 			> .mk-url-preview | ||||
| 				margin-top 8px | ||||
|  | ||||
| 			> .tags | ||||
| 				margin 4px 0 0 0 | ||||
|  | ||||
| 				> * | ||||
| 					display inline-block | ||||
| 					margin 0 8px 0 0 | ||||
| 					padding 2px 8px 2px 16px | ||||
| 					font-size 90% | ||||
| 					color #8d969e | ||||
| 					background #edf0f3 | ||||
| 					border-radius 4px | ||||
|  | ||||
| 					&:before | ||||
| 						content "" | ||||
| 						display block | ||||
| 						position absolute | ||||
| 						top 0 | ||||
| 						bottom 0 | ||||
| 						left 4px | ||||
| 						width 8px | ||||
| 						height 8px | ||||
| 						margin auto 0 | ||||
| 						background #fff | ||||
| 						border-radius 100% | ||||
|  | ||||
| 					&:hover | ||||
| 						text-decoration none | ||||
| 						background #e2e7ec | ||||
|  | ||||
| 		> footer | ||||
| 			font-size 1.2em | ||||
|  | ||||
| 			> button | ||||
| 				margin 0 28px 0 0 | ||||
| 				padding 8px | ||||
| 				background transparent | ||||
| 				border none | ||||
| 				font-size 1em | ||||
| 				color #ddd | ||||
| 				cursor pointer | ||||
|  | ||||
| 				&:hover | ||||
| 					color #666 | ||||
|  | ||||
| 				> .count | ||||
| 					display inline | ||||
| 					margin 0 0 0 8px | ||||
| 					color #999 | ||||
|  | ||||
| 				&.reacted | ||||
| 					color $theme-color | ||||
|  | ||||
| 	> .replies | ||||
| 		> * | ||||
| 			border-top 1px solid #eef0f2 | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .text | ||||
| 	cursor default | ||||
| 	display block | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	overflow-wrap break-word | ||||
| 	font-size 1.5em | ||||
| 	color #717171 | ||||
| </style> | ||||
							
								
								
									
										99
									
								
								src/client/app/desktop/views/components/post-preview.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/client/app/desktop/views/components/post-preview.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| <template> | ||||
| <div class="mk-note-preview" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-note-preview | ||||
| 	font-size 0.9em | ||||
| 	background #fff | ||||
|  | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
|  | ||||
| 	&:hover | ||||
| 		> .main > footer > button | ||||
| 			color #888 | ||||
|  | ||||
| 	> .avatar-anchor | ||||
| 		display block | ||||
| 		float left | ||||
| 		margin 0 16px 0 0 | ||||
|  | ||||
| 		> .avatar | ||||
| 			display block | ||||
| 			width 52px | ||||
| 			height 52px | ||||
| 			margin 0 | ||||
| 			border-radius 8px | ||||
| 			vertical-align bottom | ||||
|  | ||||
| 	> .main | ||||
| 		float left | ||||
| 		width calc(100% - 68px) | ||||
|  | ||||
| 		> header | ||||
| 			display flex | ||||
| 			white-space nowrap | ||||
|  | ||||
| 			> .name | ||||
| 				margin 0 .5em 0 0 | ||||
| 				padding 0 | ||||
| 				color #607073 | ||||
| 				font-size 1em | ||||
| 				font-weight bold | ||||
| 				text-decoration none | ||||
| 				white-space normal | ||||
|  | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
|  | ||||
| 			> .username | ||||
| 				margin 0 .5em 0 0 | ||||
| 				color #d1d8da | ||||
|  | ||||
| 			> .time | ||||
| 				margin-left auto | ||||
| 				color #b2b8bb | ||||
|  | ||||
| 		> .body | ||||
|  | ||||
| 			> .text | ||||
| 				cursor default | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				font-size 1.1em | ||||
| 				color #717171 | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										108
									
								
								src/client/app/desktop/views/components/posts.post.sub.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/client/app/desktop/views/components/posts.post.sub.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| <template> | ||||
| <div class="sub" :title="title"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="created-at" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.note.createdAt); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .sub | ||||
| 	margin 0 | ||||
| 	padding 16px | ||||
| 	font-size 0.9em | ||||
|  | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
|  | ||||
| 	&:hover | ||||
| 		> .main > footer > button | ||||
| 			color #888 | ||||
|  | ||||
| 	> .avatar-anchor | ||||
| 		display block | ||||
| 		float left | ||||
| 		margin 0 14px 0 0 | ||||
|  | ||||
| 		> .avatar | ||||
| 			display block | ||||
| 			width 52px | ||||
| 			height 52px | ||||
| 			margin 0 | ||||
| 			border-radius 8px | ||||
| 			vertical-align bottom | ||||
|  | ||||
| 	> .main | ||||
| 		float left | ||||
| 		width calc(100% - 66px) | ||||
|  | ||||
| 		> header | ||||
| 			display flex | ||||
| 			margin-bottom 2px | ||||
| 			white-space nowrap | ||||
| 			line-height 21px | ||||
|  | ||||
| 			> .name | ||||
| 				display block | ||||
| 				margin 0 .5em 0 0 | ||||
| 				padding 0 | ||||
| 				overflow hidden | ||||
| 				color #607073 | ||||
| 				font-size 1em | ||||
| 				font-weight bold | ||||
| 				text-decoration none | ||||
| 				text-overflow ellipsis | ||||
|  | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
|  | ||||
| 			> .username | ||||
| 				margin 0 .5em 0 0 | ||||
| 				color #d1d8da | ||||
|  | ||||
| 			> .created-at | ||||
| 				margin-left auto | ||||
| 				color #b2b8bb | ||||
|  | ||||
| 		> .body | ||||
|  | ||||
| 			> .text | ||||
| 				cursor default | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				font-size 1.1em | ||||
| 				color #717171 | ||||
|  | ||||
| 				pre | ||||
| 					max-height 120px | ||||
| 					font-size 80% | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										585
									
								
								src/client/app/desktop/views/components/posts.post.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								src/client/app/desktop/views/components/posts.post.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,585 @@ | ||||
| <template> | ||||
| <div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> | ||||
| 	<div class="reply-to" v-if="p.reply"> | ||||
| 		<x-sub :note="p.reply"/> | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet% | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span> | ||||
| 			<a class="name" :href="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</a> | ||||
| 			<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span> | ||||
| 		</p> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> | ||||
| 		</router-link> | ||||
| 		<div class="main"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> | ||||
| 				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span> | ||||
| 				<span class="username">@{{ p.user | acct }}</span> | ||||
| 				<div class="info"> | ||||
| 					<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> | ||||
| 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> | ||||
| 					<router-link class="created-at" :to="url"> | ||||
| 						<mk-time :time="p.createdAt"/> | ||||
| 					</router-link> | ||||
| 				</div> | ||||
| 			</header> | ||||
| 			<div class="body"> | ||||
| 				<p class="channel" v-if="p.channel"> | ||||
| 					<a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>: | ||||
| 				</p> | ||||
| 				<div class="text"> | ||||
| 					<a class="reply" v-if="p.reply">%fa:reply%</a> | ||||
| 					<mk-note-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.renote">RP:</a> | ||||
| 				</div> | ||||
| 				<div class="media" v-if="p.media.length > 0"> | ||||
| 					<mk-media-list :media-list="p.media"/> | ||||
| 				</div> | ||||
| 				<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> | ||||
| 				<div class="tags" v-if="p.tags && p.tags.length > 0"> | ||||
| 					<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link> | ||||
| 				</div> | ||||
| 				<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> | ||||
| 				<div class="map" v-if="p.geo" ref="map"></div> | ||||
| 				<div class="renote" v-if="p.renote"> | ||||
| 					<mk-note-preview :note="p.renote"/> | ||||
| 				</div> | ||||
| 				<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| 			</div> | ||||
| 			<footer> | ||||
| 				<mk-reactions-viewer :note="p" ref="reactionsViewer"/> | ||||
| 				<button @click="reply" title="%i18n:desktop.tags.mk-timeline-note.reply%"> | ||||
| 					%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p> | ||||
| 				</button> | ||||
| 				<button @click="renote" title="%i18n:desktop.tags.mk-timeline-note.renote%"> | ||||
| 					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p> | ||||
| 				</button> | ||||
| 				<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:desktop.tags.mk-timeline-note.add-reaction%"> | ||||
| 					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p> | ||||
| 				</button> | ||||
| 				<button @click="menu" ref="menuButton"> | ||||
| 					%fa:ellipsis-h% | ||||
| 				</button> | ||||
| 				<button title="%i18n:desktop.tags.mk-timeline-note.detail"> | ||||
| 					<template v-if="!isDetailOpened">%fa:caret-down%</template> | ||||
| 					<template v-if="isDetailOpened">%fa:caret-up%</template> | ||||
| 				</button> | ||||
| 			</footer> | ||||
| 		</div> | ||||
| 	</article> | ||||
| 	<div class="detail" v-if="isDetailOpened"> | ||||
| 		<mk-note-status-graph width="462" height="130" :note="p"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import parse from '../../../../../text/parse'; | ||||
|  | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRenoteFormWindow from './renote-form-window.vue'; | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './notes.note.sub.vue'; | ||||
|  | ||||
| function focus(el, fn) { | ||||
| 	const target = fn(el); | ||||
| 	if (target) { | ||||
| 		if (target.hasAttribute('tabindex')) { | ||||
| 			target.focus(); | ||||
| 		} else { | ||||
| 			focus(target, fn); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
|  | ||||
| 	props: ['note'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isDetailOpened: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
| 				? Object.keys(this.p.reactionCounts) | ||||
| 					.map(key => this.p.reactionCounts[key]) | ||||
| 					.reduce((a, b) => a + b) | ||||
| 				: 0; | ||||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		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.clientSettings.showMaps : true; | ||||
| 			if (shouldShowMap) { | ||||
| 				(this as any).os.getGoogleMaps().then(maps => { | ||||
| 					const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); | ||||
| 					const map = new maps.Map(this.$refs.map, { | ||||
| 						center: uluru, | ||||
| 						zoom: 15 | ||||
| 					}); | ||||
| 					new maps.Marker({ | ||||
| 						position: uluru, | ||||
| 						map: map | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.decapture(true); | ||||
|  | ||||
| 		if ((this as any).os.isSignedIn) { | ||||
| 			this.connection.off('_connected_', this.onStreamConnected); | ||||
| 			(this as any).os.stream.dispose(this.connectionId); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		capture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
| 					type: 'capture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		decapture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
| 					type: 'decapture', | ||||
| 					id: this.p.id | ||||
| 				}); | ||||
| 				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 		onStreamConnected() { | ||||
| 			this.capture(); | ||||
| 		}, | ||||
| 		onStreamNoteUpdated(data) { | ||||
| 			const note = data.note; | ||||
| 			if (note.id == this.note.id) { | ||||
| 				this.$emit('update:note', note); | ||||
| 			} else if (note.id == this.note.renoteId) { | ||||
| 				this.note.renote = note; | ||||
| 			} | ||||
| 		}, | ||||
| 		reply() { | ||||
| 			(this as any).os.new(MkPostFormWindow, { | ||||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		renote() { | ||||
| 			(this as any).os.new(MkRenoteFormWindow, { | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 		onKeydown(e) { | ||||
| 			let shouldBeCancel = true; | ||||
|  | ||||
| 			switch (true) { | ||||
| 				case e.which == 38: // [↑] | ||||
| 				case e.which == 74: // [j] | ||||
| 				case e.which == 9 && e.shiftKey: // [Shift] + [Tab] | ||||
| 					focus(this.$el, e => e.previousElementSibling); | ||||
| 					break; | ||||
|  | ||||
| 				case e.which == 40: // [↓] | ||||
| 				case e.which == 75: // [k] | ||||
| 				case e.which == 9: // [Tab] | ||||
| 					focus(this.$el, e => e.nextElementSibling); | ||||
| 					break; | ||||
|  | ||||
| 				case e.which == 81: // [q] | ||||
| 				case e.which == 69: // [e] | ||||
| 					this.renote(); | ||||
| 					break; | ||||
|  | ||||
| 				case e.which == 70: // [f] | ||||
| 				case e.which == 76: // [l] | ||||
| 					//this.like(); | ||||
| 					break; | ||||
|  | ||||
| 				case e.which == 82: // [r] | ||||
| 					this.reply(); | ||||
| 					break; | ||||
|  | ||||
| 				default: | ||||
| 					shouldBeCancel = false; | ||||
| 			} | ||||
|  | ||||
| 			if (shouldBeCancel) e.preventDefault(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .note | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	background #fff | ||||
| 	border-bottom solid 1px #eaeaea | ||||
|  | ||||
| 	&:first-child | ||||
| 		border-top-left-radius 6px | ||||
| 		border-top-right-radius 6px | ||||
|  | ||||
| 		> .renote | ||||
| 			border-top-left-radius 6px | ||||
| 			border-top-right-radius 6px | ||||
|  | ||||
| 	&:last-of-type | ||||
| 		border-bottom none | ||||
|  | ||||
| 	&:focus | ||||
| 		z-index 1 | ||||
|  | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			pointer-events none | ||||
| 			position absolute | ||||
| 			top 2px | ||||
| 			right 2px | ||||
| 			bottom 2px | ||||
| 			left 2px | ||||
| 			border 2px solid rgba($theme-color, 0.3) | ||||
| 			border-radius 4px | ||||
|  | ||||
| 	> .renote | ||||
| 		color #9dbb00 | ||||
| 		background linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
|  | ||||
| 		> p | ||||
| 			margin 0 | ||||
| 			padding 16px 32px | ||||
| 			line-height 28px | ||||
|  | ||||
| 			.avatar-anchor | ||||
| 				display inline-block | ||||
|  | ||||
| 				.avatar | ||||
| 					vertical-align bottom | ||||
| 					width 28px | ||||
| 					height 28px | ||||
| 					margin 0 8px 0 0 | ||||
| 					border-radius 6px | ||||
|  | ||||
| 			[data-fa] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 			.name | ||||
| 				font-weight bold | ||||
|  | ||||
| 		> .mk-time | ||||
| 			position absolute | ||||
| 			top 16px | ||||
| 			right 32px | ||||
| 			font-size 0.9em | ||||
| 			line-height 28px | ||||
|  | ||||
| 		& + article | ||||
| 			padding-top 8px | ||||
|  | ||||
| 	> .reply-to | ||||
| 		padding 0 16px | ||||
| 		background rgba(0, 0, 0, 0.0125) | ||||
|  | ||||
| 		> .mk-note-preview | ||||
| 			background transparent | ||||
|  | ||||
| 	> article | ||||
| 		padding 28px 32px 18px 32px | ||||
|  | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			display block | ||||
| 			clear both | ||||
|  | ||||
| 		&:hover | ||||
| 			> .main > footer > button | ||||
| 				color #888 | ||||
|  | ||||
| 		> .avatar-anchor | ||||
| 			display block | ||||
| 			float left | ||||
| 			margin 0 16px 10px 0 | ||||
| 			//position -webkit-sticky | ||||
| 			//position sticky | ||||
| 			//top 74px | ||||
|  | ||||
| 			> .avatar | ||||
| 				display block | ||||
| 				width 58px | ||||
| 				height 58px | ||||
| 				margin 0 | ||||
| 				border-radius 8px | ||||
| 				vertical-align bottom | ||||
|  | ||||
| 		> .main | ||||
| 			float left | ||||
| 			width calc(100% - 74px) | ||||
|  | ||||
| 			> header | ||||
| 				display flex | ||||
| 				align-items center | ||||
| 				margin-bottom 4px | ||||
| 				white-space nowrap | ||||
|  | ||||
| 				> .name | ||||
| 					display block | ||||
| 					margin 0 .5em 0 0 | ||||
| 					padding 0 | ||||
| 					overflow hidden | ||||
| 					color #627079 | ||||
| 					font-size 1em | ||||
| 					font-weight bold | ||||
| 					text-decoration none | ||||
| 					text-overflow ellipsis | ||||
|  | ||||
| 					&:hover | ||||
| 						text-decoration underline | ||||
|  | ||||
| 				> .is-bot | ||||
| 					margin 0 .5em 0 0 | ||||
| 					padding 1px 6px | ||||
| 					font-size 12px | ||||
| 					color #aaa | ||||
| 					border solid 1px #ddd | ||||
| 					border-radius 3px | ||||
|  | ||||
| 				> .username | ||||
| 					margin 0 .5em 0 0 | ||||
| 					color #ccc | ||||
|  | ||||
| 				> .info | ||||
| 					margin-left auto | ||||
| 					font-size 0.9em | ||||
|  | ||||
| 					> .mobile | ||||
| 						margin-right 8px | ||||
| 						color #ccc | ||||
|  | ||||
| 					> .app | ||||
| 						margin-right 8px | ||||
| 						padding-right 8px | ||||
| 						color #ccc | ||||
| 						border-right solid 1px #eaeaea | ||||
|  | ||||
| 					> .created-at | ||||
| 						color #c0c0c0 | ||||
|  | ||||
| 			> .body | ||||
|  | ||||
| 				> .text | ||||
| 					cursor default | ||||
| 					display block | ||||
| 					margin 0 | ||||
| 					padding 0 | ||||
| 					overflow-wrap break-word | ||||
| 					font-size 1.1em | ||||
| 					color #717171 | ||||
|  | ||||
| 					>>> .quote | ||||
| 						margin 8px | ||||
| 						padding 6px 12px | ||||
| 						color #aaa | ||||
| 						border-left solid 3px #eee | ||||
|  | ||||
| 					> .reply | ||||
| 						margin-right 8px | ||||
| 						color #717171 | ||||
|  | ||||
| 					> .rp | ||||
| 						margin-left 4px | ||||
| 						font-style oblique | ||||
| 						color #a0bf46 | ||||
|  | ||||
| 				> .location | ||||
| 					margin 4px 0 | ||||
| 					font-size 12px | ||||
| 					color #ccc | ||||
|  | ||||
| 				> .map | ||||
| 					width 100% | ||||
| 					height 300px | ||||
|  | ||||
| 					&:empty | ||||
| 						display none | ||||
|  | ||||
| 				> .tags | ||||
| 					margin 4px 0 0 0 | ||||
|  | ||||
| 					> * | ||||
| 						display inline-block | ||||
| 						margin 0 8px 0 0 | ||||
| 						padding 2px 8px 2px 16px | ||||
| 						font-size 90% | ||||
| 						color #8d969e | ||||
| 						background #edf0f3 | ||||
| 						border-radius 4px | ||||
|  | ||||
| 						&:before | ||||
| 							content "" | ||||
| 							display block | ||||
| 							position absolute | ||||
| 							top 0 | ||||
| 							bottom 0 | ||||
| 							left 4px | ||||
| 							width 8px | ||||
| 							height 8px | ||||
| 							margin auto 0 | ||||
| 							background #fff | ||||
| 							border-radius 100% | ||||
|  | ||||
| 						&:hover | ||||
| 							text-decoration none | ||||
| 							background #e2e7ec | ||||
|  | ||||
| 				.mk-url-preview | ||||
| 					margin-top 8px | ||||
|  | ||||
| 				> .channel | ||||
| 					margin 0 | ||||
|  | ||||
| 				> .mk-poll | ||||
| 					font-size 80% | ||||
|  | ||||
| 				> .renote | ||||
| 					margin 8px 0 | ||||
|  | ||||
| 					> .mk-note-preview | ||||
| 						padding 16px | ||||
| 						border dashed 1px #c0dac6 | ||||
| 						border-radius 8px | ||||
|  | ||||
| 			> footer | ||||
| 				> button | ||||
| 					margin 0 28px 0 0 | ||||
| 					padding 0 8px | ||||
| 					line-height 32px | ||||
| 					font-size 1em | ||||
| 					color #ddd | ||||
| 					background transparent | ||||
| 					border none | ||||
| 					cursor pointer | ||||
|  | ||||
| 					&:hover | ||||
| 						color #666 | ||||
|  | ||||
| 					> .count | ||||
| 						display inline | ||||
| 						margin 0 0 0 8px | ||||
| 						color #999 | ||||
|  | ||||
| 					&.reacted | ||||
| 						color $theme-color | ||||
|  | ||||
| 					&:last-child | ||||
| 						position absolute | ||||
| 						right 0 | ||||
| 						margin 0 | ||||
|  | ||||
| 	> .detail | ||||
| 		padding-top 4px | ||||
| 		background rgba(0, 0, 0, 0.0125) | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .text | ||||
|  | ||||
| 	code | ||||
| 		padding 4px 8px | ||||
| 		margin 0 0.5em | ||||
| 		font-size 80% | ||||
| 		color #525252 | ||||
| 		background #f8f8f8 | ||||
| 		border-radius 2px | ||||
|  | ||||
| 	pre > code | ||||
| 		padding 16px | ||||
| 		margin 0 | ||||
|  | ||||
| 	[data-is-me]:after | ||||
| 		content "you" | ||||
| 		padding 0 4px | ||||
| 		margin-left 4px | ||||
| 		font-size 80% | ||||
| 		color $theme-color-foreground | ||||
| 		background $theme-color | ||||
| 		border-radius 4px | ||||
| </style> | ||||
| @@ -5,7 +5,7 @@ | ||||
| 	</div> | ||||
| 	<div class="users" v-if="users.length != 0"> | ||||
| 		<div v-for="user in users" :key="user.id"> | ||||
| 			<p><b>{{ getUserName(user) }}</b> @{{ getAcct(user) }}</p> | ||||
| 			<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| @@ -13,8 +13,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -23,10 +21,6 @@ export default Vue.extend({ | ||||
| 			users: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('mute/list').then(x => { | ||||
| 			this.users = x.users; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 	<div class="main" ref="main"> | ||||
| 		<div class="backdrop"></div> | ||||
| 		<div class="main"> | ||||
| 			<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p> | ||||
| 			<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i | userName }}</b>さん</p> | ||||
| 			<div class="container" ref="mainContainer"> | ||||
| 				<div class="left"> | ||||
| 					<x-nav/> | ||||
| @@ -33,14 +33,7 @@ import XNotifications from './ui.header.notifications.vue'; | ||||
| import XPost from './ui.header.post.vue'; | ||||
| import XClock from './ui.header.clock.vue'; | ||||
|  | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	computed: { | ||||
| 		name(): string { | ||||
| 			return getUserName((this as any).os.i); | ||||
| 		} | ||||
| 	}, | ||||
| 	components: { | ||||
| 		XNav, | ||||
| 		XSearch, | ||||
|   | ||||
| @@ -2,12 +2,12 @@ | ||||
| <div class="mk-user-preview"> | ||||
| 	<template v-if="u != null"> | ||||
| 		<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div> | ||||
| 		<router-link class="avatar" :to="`/@${getAcct(u)}`"> | ||||
| 		<router-link class="avatar" :to="u | userPage"> | ||||
| 			<img :src="`${u.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="title"> | ||||
| 			<router-link class="name" :to="`/@${getAcct(u)}`">{{ u.name }}</router-link> | ||||
| 			<p class="username">@{{ getAcct(u) }}</p> | ||||
| 			<router-link class="name" :to="u | userPage">{{ u.name }}</router-link> | ||||
| 			<p class="username">@{{ u | acct }}</p> | ||||
| 		</div> | ||||
| 		<div class="description">{{ u.description }}</div> | ||||
| 		<div class="status"> | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| <template> | ||||
| <div class="root item"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="user.id"> | ||||
| 	<router-link class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> | ||||
| 		<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${acct}`" v-user-preview="user.id">{{ name }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> | ||||
| 			<span class="username">@{{ user | acct }}</span> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<p class="followed" v-if="user.isFollowed">フォローされています</p> | ||||
| @@ -19,19 +19,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['user'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
| 	<p class="title">%fa:users%%i18n:desktop.tags.mk-user.followers-you-know.title%</p> | ||||
| 	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.followers-you-know.loading%<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching && users.length > 0"> | ||||
| 	<router-link v-for="user in users" :to="`/@${getAcct(user)}`" :key="user.id"> | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)" v-user-preview="user.id"/> | ||||
| 	<router-link v-for="user in users" :to="user | userPage" :key="user.id"> | ||||
| 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName" v-user-preview="user.id"/> | ||||
| 	</router-link> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:desktop.tags.mk-user.followers-you-know.no-users%</p> | ||||
| @@ -13,8 +13,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../../acct/render'; | ||||
| import getUserName from '../../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| @@ -24,10 +22,6 @@ export default Vue.extend({ | ||||
| 			fetching: true | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/followers', { | ||||
| 			userId: this.user.id, | ||||
|   | ||||
| @@ -4,12 +4,12 @@ | ||||
| 	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.frequently-replied-users.loading%<mk-ellipsis/></p> | ||||
| 	<template v-if="!fetching && users.length != 0"> | ||||
| 		<div class="user" v-for="friend in users"> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${getAcct(friend)}`"> | ||||
| 			<router-link class="avatar-anchor" :to="friend | userPage"> | ||||
| 				<img class="avatar" :src="`${friend.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="friend.id"/> | ||||
| 			</router-link> | ||||
| 			<div class="body"> | ||||
| 				<router-link class="name" :to="`/@${getAcct(friend)}`" v-user-preview="friend.id">{{ friend.name }}</router-link> | ||||
| 				<p class="username">@{{ getAcct(friend) }}</p> | ||||
| 				<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> | ||||
| 				<p class="username">@{{ friend | acct }}</p> | ||||
| 			</div> | ||||
| 			<mk-follow-button :user="friend"/> | ||||
| 		</div> | ||||
|   | ||||
| @@ -7,8 +7,8 @@ | ||||
| 	<div class="container"> | ||||
| 		<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=150`" alt="avatar"/> | ||||
| 		<div class="title"> | ||||
| 			<p class="name">{{ name }}</p> | ||||
| 			<p class="username">@{{ acct }}</p> | ||||
| 			<p class="name">{{ user | userName }}</p> | ||||
| 			<p class="username">@{{ user | acct }}</p> | ||||
| 			<p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p> | ||||
| 		</div> | ||||
| 		<footer> | ||||
| @@ -22,19 +22,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../../acct/render'; | ||||
| import getUserName from '../../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		window.addEventListener('load', this.onScroll); | ||||
| 		window.addEventListener('scroll', this.onScroll); | ||||
|   | ||||
| @@ -45,7 +45,7 @@ export default Vue.extend({ | ||||
| 				this.user = user; | ||||
| 				this.fetching = false; | ||||
| 				Progress.done(); | ||||
| 				document.title = getUserName(user) + ' | Misskey'; | ||||
| 				document.title = getUserName(this.user) + ' | Misskey'; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| 					<p>ようこそ! <b>Misskey</b>はTwitter風ミニブログSNSです。思ったことや皆と共有したいことを投稿しましょう。タイムラインを見れば、皆の関心事をすぐにチェックすることもできます。<a :href="aboutUrl">詳しく...</a></p> | ||||
| 					<p><button class="signup" @click="signup">はじめる</button><button class="signin" @click="signin">ログイン</button></p> | ||||
| 					<div class="users"> | ||||
| 						<router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="`/@${getAcct(user)}`" v-user-preview="user.id"> | ||||
| 						<router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> | ||||
| 							<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 						</router-link> | ||||
| 					</div> | ||||
|   | ||||
| @@ -2,8 +2,8 @@ | ||||
| <div class="note"> | ||||
| 	<header> | ||||
| 		<a class="index" @click="reply">{{ note.index }}:</a> | ||||
| 		<router-link class="name" :to="`/@${acct}`" v-user-preview="note.user.id"><b>{{ name }}</b></router-link> | ||||
| 		<span>ID:<i>{{ acct }}</i></span> | ||||
| 		<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id"><b>{{ note.user | userName }}</b></router-link> | ||||
| 		<span>ID:<i>{{ note.user | acct }}</i></span> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<a v-if="note.reply">>>{{ note.reply.index }}</a> | ||||
| @@ -19,19 +19,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		reply() { | ||||
| 			this.$emit('reply', this.note); | ||||
|   | ||||
| @@ -0,0 +1,65 @@ | ||||
| <template> | ||||
| <div class="note"> | ||||
| 	<header> | ||||
| 		<a class="index" @click="reply">{{ note.index }}:</a> | ||||
| 		<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id"><b>{{ note.user | userName }}</b></router-link> | ||||
| 		<span>ID:<i>{{ note.user | acct }}</i></span> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<a v-if="note.reply">>>{{ note.reply.index }}</a> | ||||
| 		{{ note.text }} | ||||
| 		<div class="media" v-if="note.media"> | ||||
| 			<a v-for="file in note.media" :href="file.url" target="_blank"> | ||||
| 				<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/> | ||||
| 			</a> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	methods: { | ||||
| 		reply() { | ||||
| 			this.$emit('reply', this.note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .note | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	color #444 | ||||
|  | ||||
| 	> header | ||||
| 		position -webkit-sticky | ||||
| 		position sticky | ||||
| 		z-index 1 | ||||
| 		top 0 | ||||
| 		padding 8px 4px 4px 16px | ||||
| 		background rgba(255, 255, 255, 0.9) | ||||
|  | ||||
| 		> .index | ||||
| 			margin-right 0.25em | ||||
|  | ||||
| 		> .name | ||||
| 			margin-right 0.5em | ||||
| 			color #008000 | ||||
|  | ||||
| 	> div | ||||
| 		padding 0 16px 16px 16px | ||||
|  | ||||
| 		> .media | ||||
| 			> a | ||||
| 				display inline-block | ||||
|  | ||||
| 				> img | ||||
| 					max-width 100% | ||||
| 					vertical-align bottom | ||||
|  | ||||
| </style> | ||||
| @@ -15,14 +15,13 @@ | ||||
| 		title="クリックでアバター編集" | ||||
| 		v-user-preview="os.i.id" | ||||
| 	/> | ||||
| 	<router-link class="name" :to="`/@${os.i.username}`">{{ name }}</router-link> | ||||
| 	<p class="username">@{{ os.i.username }}</p> | ||||
| 	<router-link class="name" :to="os.i | userPage">{{ os.i | userName }}</router-link> | ||||
| 	<p class="username">@{{ os.i | acct }}</p> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default define({ | ||||
| 	name: 'profile', | ||||
| @@ -30,11 +29,6 @@ export default define({ | ||||
| 		design: 0 | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.os.i); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
|   | ||||
| @@ -7,12 +7,12 @@ | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<template v-else-if="users.length != 0"> | ||||
| 		<div class="user" v-for="_user in users"> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${getAcct(_user)}`"> | ||||
| 			<router-link class="avatar-anchor" :to="_user | userPage"> | ||||
| 				<img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/> | ||||
| 			</router-link> | ||||
| 			<div class="body"> | ||||
| 				<router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ getUserName(_user) }}</router-link> | ||||
| 				<p class="username">@{{ getAcct(_user) }}</p> | ||||
| 				<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link> | ||||
| 				<p class="username">@{{ _user | acct }}</p> | ||||
| 			</div> | ||||
| 			<mk-follow-button :user="_user"/> | ||||
| 		</div> | ||||
| @@ -23,8 +23,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| const limit = 3; | ||||
|  | ||||
| @@ -45,8 +43,6 @@ export default define({ | ||||
| 		this.fetch(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct, | ||||
| 		getUserName, | ||||
| 		func() { | ||||
| 			this.props.compact = !this.props.compact; | ||||
| 		}, | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| <template> | ||||
| <div class="mk-note-card"> | ||||
| 	<a :href="`/@${acct}/${note.id}`"> | ||||
| 	<a :href="note | notePage"> | ||||
| 		<header> | ||||
| 			<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3> | ||||
| 			<img :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/><h3>{{ note.user | userName }}</h3> | ||||
| 		</header> | ||||
| 		<div> | ||||
| 			{{ text }} | ||||
| @@ -15,18 +15,10 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import summary from '../../../../../renderers/get-note-summary'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		}, | ||||
| 		text(): string { | ||||
| 			return summary(this.note); | ||||
| 		} | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="root sub"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<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(note.user) }}</router-link> | ||||
| 			<span class="username">@{{ acct }}</span> | ||||
| 			<router-link class="time" :to="`/@${acct}/${note.id}`"> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -20,19 +20,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['note'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -17,24 +17,20 @@ | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 				<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 | ||||
| 			%fa:retweet%<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<header> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${pAcct}`"> | ||||
| 			<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 				<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> | ||||
| 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> | ||||
| 				<span class="username">@{{ p.user | acct }}</span> | ||||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| @@ -80,8 +76,6 @@ | ||||
|  | ||||
| <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'; | ||||
| @@ -112,18 +106,6 @@ export default Vue.extend({ | ||||
| 	}, | ||||
|  | ||||
| 	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 && | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="mk-note-preview"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<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}/${note.id}`"> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -20,19 +20,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.note.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.note.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['note'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="sub"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="`/@${getAcct(note.user)}`">{{ getUserName(note.user) }}</router-link> | ||||
| 			<span class="username">@{{ getAcct(note.user) }}</span> | ||||
| 			<router-link class="created-at" :to="`/@${getAcct(note.user)}/${note.id}`"> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="created-at" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| @@ -20,17 +20,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			getAcct, | ||||
| 			getUserName | ||||
| 		}; | ||||
| 	} | ||||
| 	props: ['note'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -5,28 +5,28 @@ | ||||
| 	</div> | ||||
| 	<div class="renote" v-if="isRenote"> | ||||
| 		<p> | ||||
| 			<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`"> | ||||
| 			<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 				<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="`/@${getAcct(note.user)}`">{{ getUserName(note.user) }}</router-link> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</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="`/@${getAcct(p.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="main"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="`/@${getAcct(p.user)}`">{{ getUserName(p.user) }}</router-link> | ||||
| 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> | ||||
| 				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span> | ||||
| 				<span class="username">@{{ getAcct(p.user) }}</span> | ||||
| 				<span class="username">@{{ p.user | acct }}</span> | ||||
| 				<div class="info"> | ||||
| 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> | ||||
| 					<router-link class="created-at" :to="url"> | ||||
| 					<router-link class="created-at" :to="p | notePage"> | ||||
| 						<mk-time :time="p.createdAt"/> | ||||
| 					</router-link> | ||||
| 				</div> | ||||
| @@ -77,8 +77,6 @@ | ||||
|  | ||||
| <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'; | ||||
| @@ -95,9 +93,7 @@ export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			getAcct, | ||||
| 			getUserName | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| @@ -118,9 +114,6 @@ export default Vue.extend({ | ||||
| 					.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); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<template v-if="notification.type == 'reaction'"> | ||||
| 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ getUserName(notification.user) }}</p> | ||||
| 			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ notification.user | userName }}</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -11,7 +11,7 @@ | ||||
| 	<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%{{ getUserName(notification.note.user) }}</p> | ||||
| 			<p>%fa:retweet%{{ notification.note.user | userName }}</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -19,7 +19,7 @@ | ||||
| 	<template v-if="notification.type == 'quote'"> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:quote-left%{{ getUserName(notification.note.user) }}</p> | ||||
| 			<p>%fa:quote-left%{{ notification.note.user | userName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -27,14 +27,14 @@ | ||||
| 	<template v-if="notification.type == 'follow'"> | ||||
| 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:user-plus%{{ getUserName(notification.user) }}</p> | ||||
| 			<p>%fa:user-plus%{{ notification.user | userName }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
|  | ||||
| 	<template v-if="notification.type == 'reply'"> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:reply%{{ getUserName(notification.note.user) }}</p> | ||||
| 			<p>%fa:reply%{{ notification.note.user | userName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -42,7 +42,7 @@ | ||||
| 	<template v-if="notification.type == 'mention'"> | ||||
| 		<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:at%{{ getUserName(notification.note.user) }}</p> | ||||
| 			<p>%fa:at%{{ notification.note.user | userName }}</p> | ||||
| 			<p class="note-preview">{{ getNoteSummary(notification.note) }}</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -50,7 +50,7 @@ | ||||
| 	<template v-if="notification.type == 'poll_vote'"> | ||||
| 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		<div class="text"> | ||||
| 			<p>%fa:chart-pie%{{ getUserName(notification.user) }}</p> | ||||
| 			<p>%fa:chart-pie%{{ notification.user | userName }}</p> | ||||
| 			<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p> | ||||
| 		</div> | ||||
| 	</template> | ||||
| @@ -60,14 +60,12 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['notification'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			getNoteSummary, | ||||
| 			getUserName | ||||
| 			getNoteSummary | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -2,13 +2,13 @@ | ||||
| <div class="mk-notification"> | ||||
| 	<div class="notification reaction" v-if="notification.type == 'reaction'"> | ||||
| 		<mk-time :time="notification.createdAt"/> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="notification.user | userPage"> | ||||
| 			<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="text"> | ||||
| 			<p> | ||||
| 				<mk-reaction-icon :reaction="notification.reaction"/> | ||||
| 				<router-link :to="`/@${getAcct(notification.user)}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 				<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note) }} | ||||
| @@ -19,13 +19,13 @@ | ||||
|  | ||||
| 	<div class="notification renote" v-if="notification.type == 'renote'"> | ||||
| 		<mk-time :time="notification.createdAt"/> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="notification.user | userPage"> | ||||
| 			<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="text"> | ||||
| 			<p> | ||||
| 				%fa:retweet% | ||||
| 				<router-link :to="`/@${getAcct(notification.user)}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 				<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right% | ||||
| @@ -39,13 +39,13 @@ | ||||
|  | ||||
| 	<div class="notification follow" v-if="notification.type == 'follow'"> | ||||
| 		<mk-time :time="notification.createdAt"/> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="notification.user | userPage"> | ||||
| 			<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="text"> | ||||
| 			<p> | ||||
| 				%fa:user-plus% | ||||
| 				<router-link :to="`/@${getAcct(notification.user)}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 				<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link> | ||||
| 			</p> | ||||
| 		</div> | ||||
| 	</div> | ||||
| @@ -60,13 +60,13 @@ | ||||
|  | ||||
| 	<div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> | ||||
| 		<mk-time :time="notification.createdAt"/> | ||||
| 		<router-link class="avatar-anchor" :to="`/@${getAcct(notification.user)}`"> | ||||
| 		<router-link class="avatar-anchor" :to="notification.user | userPage"> | ||||
| 			<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="text"> | ||||
| 			<p> | ||||
| 				%fa:chart-pie% | ||||
| 				<router-link :to="`/@${getAcct(notification.user)}`">{{ getUserName(notification.user) }}</router-link> | ||||
| 				<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link> | ||||
| 			</p> | ||||
| 			<router-link class="note-ref" :to="`/@${getAcct(notification.note.user)}/${notification.note.id}`"> | ||||
| 				%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right% | ||||
| @@ -79,16 +79,12 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getNoteSummary from '../../../../../renderers/get-note-summary'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['notification'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			getNoteSummary, | ||||
| 			getAcct, | ||||
| 			getUserName | ||||
| 			getNoteSummary | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
|   | ||||
							
								
								
									
										85
									
								
								src/client/app/mobile/views/components/post-card.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/client/app/mobile/views/components/post-card.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| <template> | ||||
| <div class="mk-note-card"> | ||||
| 	<a :href="note | notePage"> | ||||
| 		<header> | ||||
| 			<img :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/><h3>{{ note.user | userName }}</h3> | ||||
| 		</header> | ||||
| 		<div> | ||||
| 			{{ text }} | ||||
| 		</div> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 	</a> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import summary from '../../../../../renderers/get-note-summary'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'], | ||||
| 	computed: { | ||||
| 		text(): string { | ||||
| 			return summary(this.note); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-note-card | ||||
| 	display inline-block | ||||
| 	width 150px | ||||
| 	//height 120px | ||||
| 	font-size 12px | ||||
| 	background #fff | ||||
| 	border-radius 4px | ||||
|  | ||||
| 	> a | ||||
| 		display block | ||||
| 		color #2c3940 | ||||
|  | ||||
| 		&:hover | ||||
| 			text-decoration none | ||||
|  | ||||
| 		> header | ||||
| 			> img | ||||
| 				position absolute | ||||
| 				top 8px | ||||
| 				left 8px | ||||
| 				width 28px | ||||
| 				height 28px | ||||
| 				border-radius 6px | ||||
|  | ||||
| 			> h3 | ||||
| 				display inline-block | ||||
| 				overflow hidden | ||||
| 				width calc(100% - 45px) | ||||
| 				margin 8px 0 0 42px | ||||
| 				line-height 28px | ||||
| 				white-space nowrap | ||||
| 				text-overflow ellipsis | ||||
| 				font-size 12px | ||||
|  | ||||
| 		> div | ||||
| 			padding 2px 8px 8px 8px | ||||
| 			height 60px | ||||
| 			overflow hidden | ||||
| 			white-space normal | ||||
|  | ||||
| 			&:after | ||||
| 				content "" | ||||
| 				display block | ||||
| 				position absolute | ||||
| 				top 40px | ||||
| 				left 0 | ||||
| 				width 100% | ||||
| 				height 20px | ||||
| 				background linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #fff 100%) | ||||
|  | ||||
| 		> .mk-time | ||||
| 			display inline-block | ||||
| 			padding 8px | ||||
| 			color #aaa | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										103
									
								
								src/client/app/mobile/views/components/post-detail.sub.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/client/app/mobile/views/components/post-detail.sub.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| <template> | ||||
| <div class="root sub"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .root.sub | ||||
| 	padding 8px | ||||
| 	font-size 0.9em | ||||
| 	background #fdfdfd | ||||
|  | ||||
| 	@media (min-width 500px) | ||||
| 		padding 12px | ||||
|  | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
|  | ||||
| 	&:hover | ||||
| 		> .main > footer > button | ||||
| 			color #888 | ||||
|  | ||||
| 	> .avatar-anchor | ||||
| 		display block | ||||
| 		float left | ||||
| 		margin 0 12px 0 0 | ||||
|  | ||||
| 		> .avatar | ||||
| 			display block | ||||
| 			width 48px | ||||
| 			height 48px | ||||
| 			margin 0 | ||||
| 			border-radius 8px | ||||
| 			vertical-align bottom | ||||
|  | ||||
| 	> .main | ||||
| 		float left | ||||
| 		width calc(100% - 60px) | ||||
|  | ||||
| 		> header | ||||
| 			display flex | ||||
| 			margin-bottom 4px | ||||
| 			white-space nowrap | ||||
|  | ||||
| 			> .name | ||||
| 				display block | ||||
| 				margin 0 .5em 0 0 | ||||
| 				padding 0 | ||||
| 				overflow hidden | ||||
| 				color #607073 | ||||
| 				font-size 1em | ||||
| 				font-weight 700 | ||||
| 				text-align left | ||||
| 				text-decoration none | ||||
| 				text-overflow ellipsis | ||||
|  | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
|  | ||||
| 			> .username | ||||
| 				text-align left | ||||
| 				margin 0 .5em 0 0 | ||||
| 				color #d1d8da | ||||
|  | ||||
| 			> .time | ||||
| 				margin-left auto | ||||
| 				color #b2b8bb | ||||
|  | ||||
| 		> .body | ||||
|  | ||||
| 			> .text | ||||
| 				cursor default | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				font-size 1.1em | ||||
| 				color #717171 | ||||
|  | ||||
| </style> | ||||
|  | ||||
							
								
								
									
										444
									
								
								src/client/app/mobile/views/components/post-detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								src/client/app/mobile/views/components/post-detail.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,444 @@ | ||||
| <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="note.user | userPage"> | ||||
| 				<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			%fa:retweet%<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>がRenote | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<header> | ||||
| 			<router-link class="avatar-anchor" :to="p.user | userPage"> | ||||
| 				<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 			</router-link> | ||||
| 			<div> | ||||
| 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> | ||||
| 				<span class="username">@{{ p.user | acct }}</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 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: { | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				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.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> | ||||
							
								
								
									
										100
									
								
								src/client/app/mobile/views/components/post-preview.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/client/app/mobile/views/components/post-preview.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| <template> | ||||
| <div class="mk-note-preview"> | ||||
| 	<router-link class="avatar-anchor" :to="note.user | userPage"> | ||||
| 		<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> | ||||
| 	</router-link> | ||||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 			<span class="username">@{{ note.user | acct }}</span> | ||||
| 			<router-link class="time" :to="note | notePage"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-sub-note-content class="text" :note="note"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['note'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-note-preview | ||||
| 	margin 0 | ||||
| 	padding 0 | ||||
| 	font-size 0.9em | ||||
| 	background #fff | ||||
|  | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
|  | ||||
| 	&:hover | ||||
| 		> .main > footer > button | ||||
| 			color #888 | ||||
|  | ||||
| 	> .avatar-anchor | ||||
| 		display block | ||||
| 		float left | ||||
| 		margin 0 12px 0 0 | ||||
|  | ||||
| 		> .avatar | ||||
| 			display block | ||||
| 			width 48px | ||||
| 			height 48px | ||||
| 			margin 0 | ||||
| 			border-radius 8px | ||||
| 			vertical-align bottom | ||||
|  | ||||
| 	> .main | ||||
| 		float left | ||||
| 		width calc(100% - 60px) | ||||
|  | ||||
| 		> header | ||||
| 			display flex | ||||
| 			margin-bottom 4px | ||||
| 			white-space nowrap | ||||
|  | ||||
| 			> .name | ||||
| 				display block | ||||
| 				margin 0 .5em 0 0 | ||||
| 				padding 0 | ||||
| 				overflow hidden | ||||
| 				color #607073 | ||||
| 				font-size 1em | ||||
| 				font-weight 700 | ||||
| 				text-align left | ||||
| 				text-decoration none | ||||
| 				text-overflow ellipsis | ||||
|  | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
|  | ||||
| 			> .username | ||||
| 				text-align left | ||||
| 				margin 0 .5em 0 0 | ||||
| 				color #d1d8da | ||||
|  | ||||
| 			> .time | ||||
| 				margin-left auto | ||||
| 				color #b2b8bb | ||||
|  | ||||
| 		> .body | ||||
|  | ||||
| 			> .text | ||||
| 				cursor default | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| 				font-size 1.1em | ||||
| 				color #717171 | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										523
									
								
								src/client/app/mobile/views/components/post.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										523
									
								
								src/client/app/mobile/views/components/post.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,523 @@ | ||||
| <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="note.user | userPage"> | ||||
| 				<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="note.user | userPage">{{ note.user | userName }}</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="p.user | userPage"> | ||||
| 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> | ||||
| 		</router-link> | ||||
| 		<div class="main"> | ||||
| 			<header> | ||||
| 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> | ||||
| 				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span> | ||||
| 				<span class="username">@{{ p.user | acct }}</span> | ||||
| 				<div class="info"> | ||||
| 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> | ||||
| 					<router-link class="created-at" :to="p | notePage"> | ||||
| 						<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 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: { | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				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; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	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.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> | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<mk-special-message/> | ||||
| 	<div class="main" ref="main"> | ||||
| 		<div class="backdrop"></div> | ||||
| 		<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p> | ||||
| 		<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i | userName }}</b>さん</p> | ||||
| 		<div class="content" ref="mainContainer"> | ||||
| 			<button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button> | ||||
| 			<template v-if="hasUnreadNotifications || hasUnreadMessagingMessages || hasGameInvitations">%fa:circle%</template> | ||||
| @@ -19,15 +19,9 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as anime from 'animejs'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['func'], | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.os.i); | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			hasUnreadNotifications: false, | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 		<div class="body" v-if="isOpen"> | ||||
| 			<router-link class="me" v-if="os.isSignedIn" :to="`/@${os.i.username}`"> | ||||
| 				<img class="avatar" :src="`${os.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/> | ||||
| 				<p class="name">{{ name }}</p> | ||||
| 				<p class="name">{{ os.i | userName }}</p> | ||||
| 			</router-link> | ||||
| 			<div class="links"> | ||||
| 				<ul> | ||||
| @@ -39,16 +39,10 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { docsUrl, chUrl, lang } from '../../../config'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import { docsUrl, lang } from '../../../config'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['isOpen'], | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.os.i); | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			hasUnreadNotifications: false, | ||||
| @@ -56,8 +50,7 @@ export default Vue.extend({ | ||||
| 			hasGameInvitations: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			aboutUrl: `${docsUrl}/${lang}/about`, | ||||
| 			chUrl | ||||
| 			aboutUrl: `${docsUrl}/${lang}/about` | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
|   | ||||
| @@ -1,31 +1,21 @@ | ||||
| <template> | ||||
| <div class="mk-user-card"> | ||||
| 	<header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"> | ||||
| 		<a :href="`/@${acct}`"> | ||||
| 		<a :href="user | userPage"> | ||||
| 			<img :src="`${user.avatarUrl}?thumbnail&size=200`" alt="avatar"/> | ||||
| 		</a> | ||||
| 	</header> | ||||
| 	<a class="name" :href="`/@${acct}`" target="_blank">{{ name }}</a> | ||||
| 	<p class="username">@{{ acct }}</p> | ||||
| 	<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a> | ||||
| 	<p class="username">@{{ user | acct }}</p> | ||||
| 	<mk-follow-button :user="user"/> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['user'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| <template> | ||||
| <div class="mk-user-preview"> | ||||
| 	<router-link class="avatar-anchor" :to="`/@${acct}`"> | ||||
| 	<router-link class="avatar-anchor" :to="user | userPage"> | ||||
| 		<img class="avatar" :src="`${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="name" :to="user | userPage">{{ user | userName }}</router-link> | ||||
| 			<span class="username">@{{ user | acct }}</span> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<div class="description">{{ user.description }}</div> | ||||
| @@ -17,19 +17,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.user); | ||||
| 		}, | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	} | ||||
| 	props: ['user'] | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,6 @@ | ||||
| import Vue from 'vue'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import parseAcct from '../../../../../acct/parse'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -30,8 +29,8 @@ export default Vue.extend({ | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		name(): string { | ||||
| 			return Vue.filter('userName')(this.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <mk-ui> | ||||
| 	<span slot="header"> | ||||
| 		<template v-if="user">%fa:R comments%{{ name }}</template> | ||||
| 		<template v-if="user">%fa:R comments%{{ user | userName }}</template> | ||||
| 		<template v-else><mk-ellipsis/></template> | ||||
| 	</span> | ||||
| 	<mk-messaging-room v-if="!fetching" :user="user" :is-naked="true"/> | ||||
| @@ -11,7 +11,6 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import parseAcct from '../../../../../acct/parse'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -20,11 +19,6 @@ export default Vue.extend({ | ||||
| 			user: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		$route: 'fetch' | ||||
| 	}, | ||||
| @@ -39,7 +33,7 @@ export default Vue.extend({ | ||||
| 				this.user = user; | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${this.name} | Misskey`; | ||||
| 				document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${Vue.filter('userName')(this.user)} | Misskey`; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -20,7 +20,6 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { version, codename } from '../../../config'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| @@ -30,8 +29,8 @@ export default Vue.extend({ | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.os.i); | ||||
| 		name(): string { | ||||
| 			return Vue.filter('userName')((this as any).os.i); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <mk-ui> | ||||
| 	<span slot="header" v-if="!fetching">%fa:user% {{ user }}</span> | ||||
| 	<span slot="header" v-if="!fetching">%fa:user% {{ user | userName }}</span> | ||||
| 	<main v-if="!fetching"> | ||||
| 		<header> | ||||
| 			<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"></div> | ||||
| @@ -12,8 +12,8 @@ | ||||
| 					<mk-follow-button v-if="os.isSignedIn && os.i.id != user.id" :user="user"/> | ||||
| 				</div> | ||||
| 				<div class="title"> | ||||
| 					<h1>{{ getUserName(user) }}</h1> | ||||
| 					<span class="username">@{{ getAcct(user) }}</span> | ||||
| 					<h1>{{ user | userName }}</h1> | ||||
| 					<span class="username">@{{ user | acct }}</span> | ||||
| 					<span class="followed" v-if="user.isFollowed">%i18n:mobile.tags.mk-user.follows-you%</span> | ||||
| 				</div> | ||||
| 				<div class="description">{{ user.description }}</div> | ||||
| @@ -61,8 +61,6 @@ | ||||
| import Vue from 'vue'; | ||||
| import * as age from 's-age'; | ||||
| import parseAcct from '../../../../../acct/parse'; | ||||
| import getAcct from '../../../../../acct/render'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import XHome from './user/home.vue'; | ||||
|  | ||||
| @@ -74,9 +72,7 @@ export default Vue.extend({ | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			user: null, | ||||
| 			page: 'home', | ||||
| 			getAcct, | ||||
| 			getUserName | ||||
| 			page: 'home' | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| @@ -102,7 +98,7 @@ export default Vue.extend({ | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				Progress.done(); | ||||
| 				document.title = this.getUserName(this.user) + ' | Misskey'; | ||||
| 				document.title = Vue.filter('userName')(this.user) + ' | Misskey'; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -2,8 +2,8 @@ | ||||
| <div class="root followers-you-know"> | ||||
| 	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching && users.length > 0"> | ||||
| 		<a v-for="user in users" :key="user.id" :href="`/@${getAcct(user)}`"> | ||||
| 			<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)"/> | ||||
| 		<a v-for="user in users" :key="user.id" :href="user | userPage"> | ||||
| 			<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName"/> | ||||
| 		</a> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p> | ||||
| @@ -12,8 +12,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../../acct/render'; | ||||
| import getUserName from '../../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
| @@ -23,14 +21,6 @@ export default Vue.extend({ | ||||
| 			users: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.user); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getAcct | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/followers', { | ||||
| 			userId: this.user.id, | ||||
|   | ||||
| @@ -8,23 +8,16 @@ | ||||
| 			:src="`${os.i.avatarUrl}?thumbnail&size=96`" | ||||
| 			alt="avatar" | ||||
| 		/> | ||||
| 		<router-link :class="$style.name" :to="`/@${os.i.username}`">{{ name }}</router-link> | ||||
| 		<router-link :class="$style.name" :to="os.i | userPage">{{ os.i | userName }}</router-link> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import getUserName from '../../../../../renderers/get-user-name'; | ||||
|  | ||||
| export default define({ | ||||
| 	name: 'profile' | ||||
| }).extend({ | ||||
| 	computed: { | ||||
| 		name() { | ||||
| 			return getUserName(this.os.i); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo