parent
							
								
									eafb0f61ef
								
							
						
					
					
						commit
						3178bb20c7
					
				|  | @ -4,7 +4,7 @@ import signin from './signin.vue'; | |||
| import signup from './signup.vue'; | ||||
| import forkit from './forkit.vue'; | ||||
| import nav from './nav.vue'; | ||||
| import postHtml from './post-html.vue'; | ||||
| import postHtml from './post-html'; | ||||
| import poll from './poll.vue'; | ||||
| import pollEditor from './poll-editor.vue'; | ||||
| import reactionIcon from './reaction-icon.vue'; | ||||
|  |  | |||
|  | @ -4,13 +4,13 @@ | |||
| 		<img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/> | ||||
| 	</router-link> | ||||
| 	<div class="content"> | ||||
| 		<div class="balloon" :data-no-text="message.textHtml == null"> | ||||
| 		<div class="balloon" :data-no-text="message.text == null"> | ||||
| 			<p class="read" v-if="isMe && message.isRead">%i18n:common.tags.mk-messaging-message.is-read%</p> | ||||
| 			<button class="delete-button" v-if="isMe" title="%i18n:common.delete%"> | ||||
| 				<img src="/assets/desktop/messaging/delete.png" alt="Delete"/> | ||||
| 			</button> | ||||
| 			<div class="content" v-if="!message.isDeleted"> | ||||
| 				<mk-post-html class="text" v-if="message.textHtml" ref="text" :html="message.textHtml" :i="os.i"/> | ||||
| 				<mk-post-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/> | ||||
| 				<div class="file" v-if="message.file"> | ||||
| 					<a :href="message.file.url" target="_blank" :title="message.file.name"> | ||||
| 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> | ||||
|  | @ -35,35 +35,30 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['message'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			urls: [] | ||||
| 		}; | ||||
| 	props: { | ||||
| 		message: { | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.message.user); | ||||
| 		}, | ||||
| 		isMe(): boolean { | ||||
| 			return this.message.userId == (this as any).os.i.id; | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		message: { | ||||
| 			handler(newMessage, oldMessage) { | ||||
| 				if (!oldMessage || newMessage.textHtml !== oldMessage.textHtml) { | ||||
| 					this.$nextTick(() => { | ||||
| 						const elements = this.$refs.text.$el.getElementsByTagName('a'); | ||||
| 
 | ||||
| 						this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) | ||||
| 							.map(({ href }) => href); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			immediate: true | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.message.text) { | ||||
| 				const ast = parse(this.message.text); | ||||
| 				return ast | ||||
| 					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) | ||||
| 					.map(t => t.url); | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -0,0 +1,157 @@ | |||
| import Vue from 'vue'; | ||||
| import * as emojilib from 'emojilib'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import { url } from '../../../config'; | ||||
| import MkUrl from './url.vue'; | ||||
| 
 | ||||
| const flatten = list => list.reduce( | ||||
| 	(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] | ||||
| ); | ||||
| 
 | ||||
| export default Vue.component('mk-post-html', { | ||||
| 	props: { | ||||
| 		text: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		ast: { | ||||
| 			type: [], | ||||
| 			required: false | ||||
| 		}, | ||||
| 		shouldBreak: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		i: { | ||||
| 			type: Object, | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	render(createElement) { | ||||
| 		let ast; | ||||
| 
 | ||||
| 		if (this.ast == null) { | ||||
| 			// Parse text to ast
 | ||||
| 			ast = parse(this.text); | ||||
| 		} else { | ||||
| 			ast = this.ast; | ||||
| 		} | ||||
| 
 | ||||
| 		// Parse ast to DOM
 | ||||
| 		const els = flatten(ast.map(token => { | ||||
| 			switch (token.type) { | ||||
| 				case 'text': | ||||
| 					const text = token.content.replace(/(\r\n|\n|\r)/g, '\n'); | ||||
| 
 | ||||
| 					if (this.shouldBreak) { | ||||
| 						const x = text.split('\n') | ||||
| 							.map(t => t == '' ? [createElement('br')] : [createElement('span', t), createElement('br')]); | ||||
| 						x[x.length - 1].pop(); | ||||
| 						return x; | ||||
| 					} else { | ||||
| 						return createElement('span', text.replace(/\n/g, ' ')); | ||||
| 					} | ||||
| 
 | ||||
| 				case 'bold': | ||||
| 					return createElement('strong', token.bold); | ||||
| 
 | ||||
| 				case 'url': | ||||
| 					return createElement(MkUrl, { | ||||
| 						props: { | ||||
| 							url: token.content, | ||||
| 							target: '_blank' | ||||
| 						} | ||||
| 					}); | ||||
| 
 | ||||
| 				case 'link': | ||||
| 					return createElement('a', { | ||||
| 						attrs: { | ||||
| 							class: 'link', | ||||
| 							href: token.url, | ||||
| 							target: '_blank', | ||||
| 							title: token.url | ||||
| 						} | ||||
| 					}, token.title); | ||||
| 
 | ||||
| 				case 'mention': | ||||
| 					return (createElement as any)('a', { | ||||
| 						attrs: { | ||||
| 							href: `${url}/@${getAcct(token)}`, | ||||
| 							target: '_blank', | ||||
| 							dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token) | ||||
| 						}, | ||||
| 						directives: [{ | ||||
| 							name: 'user-preview', | ||||
| 							value: token.content | ||||
| 						}] | ||||
| 					}, token.content); | ||||
| 
 | ||||
| 				case 'hashtag': | ||||
| 					return createElement('a', { | ||||
| 						attrs: { | ||||
| 							href: `${url}/search?q=${token.content}`, | ||||
| 							target: '_blank' | ||||
| 						} | ||||
| 					}, token.content); | ||||
| 
 | ||||
| 				case 'code': | ||||
| 					return createElement('pre', [ | ||||
| 						createElement('code', { | ||||
| 							domProps: { | ||||
| 								innerHTML: token.html | ||||
| 							} | ||||
| 						}) | ||||
| 					]); | ||||
| 
 | ||||
| 				case 'inline-code': | ||||
| 					return createElement('code', { | ||||
| 						domProps: { | ||||
| 							innerHTML: token.html | ||||
| 						} | ||||
| 					}); | ||||
| 
 | ||||
| 				case 'quote': | ||||
| 					const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n'); | ||||
| 
 | ||||
| 					if (this.shouldBreak) { | ||||
| 						const x = text2.split('\n') | ||||
| 							.map(t => [createElement('span', t), createElement('br')]); | ||||
| 						x[x.length - 1].pop(); | ||||
| 						return createElement('div', { | ||||
| 							attrs: { | ||||
| 								class: 'quote' | ||||
| 							} | ||||
| 						}, x); | ||||
| 					} else { | ||||
| 						return createElement('span', { | ||||
| 							attrs: { | ||||
| 								class: 'quote' | ||||
| 							} | ||||
| 						}, text2.replace(/\n/g, ' ')); | ||||
| 					} | ||||
| 
 | ||||
| 				case 'emoji': | ||||
| 					const emoji = emojilib.lib[token.emoji]; | ||||
| 					return createElement('span', emoji ? emoji.char : token.content); | ||||
| 
 | ||||
| 				default: | ||||
| 					console.log('unknown ast type:', token.type); | ||||
| 			} | ||||
| 		})); | ||||
| 
 | ||||
| 		const _els = []; | ||||
| 		els.forEach((el, i) => { | ||||
| 			if (el.tag == 'br') { | ||||
| 				if (els[i - 1].tag != 'div') { | ||||
| 					_els.push(el); | ||||
| 				} | ||||
| 			} else { | ||||
| 				_els.push(el); | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		return createElement('span', _els); | ||||
| 	} | ||||
| }); | ||||
|  | @ -1,103 +0,0 @@ | |||
| <template><div class="mk-post-html" v-html="html"></div></template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import { url } from '../../../config'; | ||||
| 
 | ||||
| function markUrl(a) { | ||||
| 	while (a.firstChild) { | ||||
| 		a.removeChild(a.firstChild); | ||||
| 	} | ||||
| 
 | ||||
| 	const schema = document.createElement('span'); | ||||
| 	const delimiter = document.createTextNode('//'); | ||||
| 	const host = document.createElement('span'); | ||||
| 	const pathname = document.createElement('span'); | ||||
| 	const query = document.createElement('span'); | ||||
| 	const hash = document.createElement('span'); | ||||
| 
 | ||||
| 	schema.className = 'schema'; | ||||
| 	schema.textContent = a.protocol; | ||||
| 
 | ||||
| 	host.className = 'host'; | ||||
| 	host.textContent = a.host; | ||||
| 
 | ||||
| 	pathname.className = 'pathname'; | ||||
| 	pathname.textContent = a.pathname; | ||||
| 
 | ||||
| 	query.className = 'query'; | ||||
| 	query.textContent = a.search; | ||||
| 
 | ||||
| 	hash.className = 'hash'; | ||||
| 	hash.textContent = a.hash; | ||||
| 
 | ||||
| 	a.appendChild(schema); | ||||
| 	a.appendChild(delimiter); | ||||
| 	a.appendChild(host); | ||||
| 	a.appendChild(pathname); | ||||
| 	a.appendChild(query); | ||||
| 	a.appendChild(hash); | ||||
| } | ||||
| 
 | ||||
| function markMe(me, a) { | ||||
| 	a.setAttribute("data-is-me", me && `${url}/@${getAcct(me)}` == a.href); | ||||
| } | ||||
| 
 | ||||
| function markTarget(a) { | ||||
| 	a.setAttribute("target", "_blank"); | ||||
| } | ||||
| 
 | ||||
| export default Vue.component('mk-post-html', { | ||||
| 	props: { | ||||
| 		html: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		i: { | ||||
| 			type: Object, | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
| 	watch { | ||||
| 		html: { | ||||
| 			handler() { | ||||
| 				this.$nextTick(() => [].forEach.call(this.$el.getElementsByTagName('a'), a => { | ||||
| 					if (a.href === a.textContent) { | ||||
| 						markUrl(a); | ||||
| 					} else { | ||||
| 						markMe((this as any).i, a); | ||||
| 					} | ||||
| 
 | ||||
| 					markTarget(a); | ||||
| 				})); | ||||
| 			}, | ||||
| 			immediate: true, | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus"> | ||||
| .mk-post-html | ||||
| 	a | ||||
| 		word-break break-all | ||||
| 
 | ||||
| 		> .schema | ||||
| 			opacity 0.5 | ||||
| 
 | ||||
| 		> .host | ||||
| 			font-weight bold | ||||
| 
 | ||||
| 		> .pathname | ||||
| 			opacity 0.8 | ||||
| 
 | ||||
| 		> .query | ||||
| 			opacity 0.5 | ||||
| 
 | ||||
| 		> .hash | ||||
| 			font-style italic | ||||
| 
 | ||||
| 	p | ||||
| 		margin 0 | ||||
| </style> | ||||
|  | @ -0,0 +1,57 @@ | |||
| <template> | ||||
| <a class="mk-url" :href="url" :target="target"> | ||||
| 	<span class="schema">{{ schema }}//</span> | ||||
| 	<span class="hostname">{{ hostname }}</span> | ||||
| 	<span class="port" v-if="port != ''">:{{ port }}</span> | ||||
| 	<span class="pathname" v-if="pathname != ''">{{ pathname }}</span> | ||||
| 	<span class="query">{{ query }}</span> | ||||
| 	<span class="hash">{{ hash }}</span> | ||||
| 	%fa:external-link-square-alt% | ||||
| </a> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: ['url', 'target'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			schema: null, | ||||
| 			hostname: null, | ||||
| 			port: null, | ||||
| 			pathname: null, | ||||
| 			query: null, | ||||
| 			hash: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		const url = new URL(this.url); | ||||
| 		this.schema = url.protocol; | ||||
| 		this.hostname = url.hostname; | ||||
| 		this.port = url.port; | ||||
| 		this.pathname = url.pathname; | ||||
| 		this.query = url.search; | ||||
| 		this.hash = url.hash; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-url | ||||
| 	word-break break-all | ||||
| 	> [data-fa] | ||||
| 		padding-left 2px | ||||
| 		font-size .9em | ||||
| 		font-weight 400 | ||||
| 		font-style normal | ||||
| 	> .schema | ||||
| 		opacity 0.5 | ||||
| 	> .hostname | ||||
| 		font-weight bold | ||||
| 	> .pathname | ||||
| 		opacity 0.8 | ||||
| 	> .query | ||||
| 		opacity 0.5 | ||||
| 	> .hash | ||||
| 		font-style italic | ||||
| </style> | ||||
|  | @ -15,7 +15,7 @@ | |||
| 				</div> | ||||
| 			</header> | ||||
| 			<div class="text"> | ||||
| 				<mk-post-html :html="post.textHtml"/> | ||||
| 				<mk-post-html :text="post.text"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-post-html v-if="post.textHtml" :html="post.textHtml" :i="os.i" :class="$style.text"/> | ||||
| 			<mk-post-html v-if="post.text" :text="post.text" :i="os.i" :class="$style.text"/> | ||||
| 			<div class="media" v-if="post.media > 0"> | ||||
| 				<mk-media-list :media-list="post.media"/> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ | |||
| 			</router-link> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
| 			<mk-post-html :class="$style.text" v-if="p.text" ref="text" :text="p.text" :i="os.i"/> | ||||
| 			<mk-post-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> | ||||
|  | @ -79,6 +79,7 @@ | |||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| 
 | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRepostFormWindow from './repost-form-window.vue'; | ||||
|  | @ -90,6 +91,7 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		post: { | ||||
| 			type: Object, | ||||
|  | @ -99,19 +101,15 @@ export default Vue.extend({ | |||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 			return getAcct(this.post.user); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			context: [], | ||||
| 			contextFetching: false, | ||||
| 			replies: [], | ||||
| 			urls: [] | ||||
| 			replies: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		isRepost(): boolean { | ||||
| 			return (this.post.repost && | ||||
|  | @ -131,8 +129,22 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
| 				return ast | ||||
| 					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) | ||||
| 					.map(t => t.url); | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		// Get replies | ||||
| 		if (!this.compact) { | ||||
|  | @ -162,21 +174,7 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		post: { | ||||
| 			handler(newPost, oldPost) { | ||||
| 				if (!oldPost || newPost.text !== oldPost.text) { | ||||
| 					this.$nextTick(() => { | ||||
| 						const elements = this.$refs.text.$el.getElementsByTagName('a'); | ||||
| 
 | ||||
| 						this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) | ||||
| 							.map(({ href }) => href); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			immediate: true | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		fetchContext() { | ||||
| 			this.contextFetching = true; | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ | |||
| 				</p> | ||||
| 				<div class="text"> | ||||
| 					<a class="reply" v-if="p.reply">%fa:reply%</a> | ||||
| 					<mk-post-html v-if="p.textHtml" ref="text" :html="p.textHtml" :i="os.i" :class="$style.text"/> | ||||
| 					<mk-post-html v-if="p.textHtml" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.repost">RP:</a> | ||||
| 				</div> | ||||
| 				<div class="media" v-if="p.media.length > 0"> | ||||
|  | @ -86,6 +86,8 @@ | |||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| 
 | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
| import MkRepostFormWindow from './repost-form-window.vue'; | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
|  | @ -107,17 +109,19 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: ['post'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isDetailOpened: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			urls: [] | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
|  | @ -141,14 +145,26 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		url(): string { | ||||
| 			return `/@${this.acct}/${this.p.id}`; | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
| 				return ast | ||||
| 					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) | ||||
| 					.map(t => t.url); | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		if ((this as any).os.isSignedIn) { | ||||
| 			this.connection = (this as any).os.stream.getConnection(); | ||||
| 			this.connectionId = (this as any).os.stream.use(); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.capture(true); | ||||
| 
 | ||||
|  | @ -174,6 +190,7 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeDestroy() { | ||||
| 		this.decapture(true); | ||||
| 
 | ||||
|  | @ -182,21 +199,7 @@ export default Vue.extend({ | |||
| 			(this as any).os.stream.dispose(this.connectionId); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		post: { | ||||
| 			handler(newPost, oldPost) { | ||||
| 				if (!oldPost || newPost.textHtml !== oldPost.textHtml) { | ||||
| 					this.$nextTick(() => { | ||||
| 						const elements = this.$refs.text.$el.getElementsByTagName('a'); | ||||
| 
 | ||||
| 						this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) | ||||
| 							.map(({ href }) => href); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			immediate: true | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		capture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
|  | @ -457,7 +460,7 @@ export default Vue.extend({ | |||
| 					font-size 1.1em | ||||
| 					color #717171 | ||||
| 
 | ||||
| 					>>> blockquote | ||||
| 					>>> .quote | ||||
| 						margin 8px | ||||
| 						padding 6px 12px | ||||
| 						color #aaa | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <div class="mk-sub-post-content"> | ||||
| 	<div class="body"> | ||||
| 		<a class="reply" v-if="post.replyId">%fa:reply%</a> | ||||
| 		<mk-post-html ref="text" :html="post.textHtml" :i="os.i"/> | ||||
| 		<mk-post-html :text="post.text" :i="os.i"/> | ||||
| 		<a class="rp" v-if="post.repostId" :href="`/post:${post.repostId}`">RP: ...</a> | ||||
| 	</div> | ||||
| 	<details v-if="post.media.length > 0"> | ||||
|  |  | |||
|  | @ -81,6 +81,8 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| 
 | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './post-detail.sub.vue'; | ||||
|  | @ -89,6 +91,7 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		post: { | ||||
| 			type: Object, | ||||
|  | @ -98,19 +101,20 @@ export default Vue.extend({ | |||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			context: [], | ||||
| 			contextFetching: false, | ||||
| 			replies: [], | ||||
| 			urls: [] | ||||
| 			replies: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.post.user); | ||||
| 		}, | ||||
| 		pAcct() { | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
|  | @ -128,8 +132,19 @@ export default Vue.extend({ | |||
| 					.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) { | ||||
|  | @ -159,21 +174,7 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		post: { | ||||
| 			handler(newPost, oldPost) { | ||||
| 				if (!oldPost || newPost.text !== oldPost.text) { | ||||
| 					this.$nextTick(() => { | ||||
| 						const elements = this.$refs.text.$el.getElementsByTagName('a'); | ||||
| 
 | ||||
| 						this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) | ||||
| 							.map(({ href }) => href); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			immediate: true | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		fetchContext() { | ||||
| 			this.contextFetching = true; | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ | |||
| 					<a class="reply" v-if="p.reply"> | ||||
| 						%fa:reply% | ||||
| 					</a> | ||||
| 					<mk-post-html v-if="p.text" ref="text" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<mk-post-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> | ||||
| 					<a class="rp" v-if="p.repost != null">RP:</a> | ||||
| 				</div> | ||||
| 				<div class="media" v-if="p.media.length > 0"> | ||||
|  | @ -78,6 +78,8 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import getAcct from '../../../../../common/user/get-acct'; | ||||
| import parse from '../../../../../common/text/parse'; | ||||
| 
 | ||||
| import MkPostMenu from '../../../common/views/components/post-menu.vue'; | ||||
| import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; | ||||
| import XSub from './post.sub.vue'; | ||||
|  | @ -86,19 +88,21 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XSub | ||||
| 	}, | ||||
| 
 | ||||
| 	props: ['post'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			urls: [] | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		acct() { | ||||
| 		acct(): string { | ||||
| 			return getAcct(this.post.user); | ||||
| 		}, | ||||
| 		pAcct() { | ||||
| 		pAcct(): string { | ||||
| 			return getAcct(this.p.user); | ||||
| 		}, | ||||
| 		isRepost(): boolean { | ||||
|  | @ -119,14 +123,26 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		url(): string { | ||||
| 			return `/@${this.pAcct}/${this.p.id}`; | ||||
| 		}, | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
| 				return ast | ||||
| 					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) | ||||
| 					.map(t => t.url); | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		if ((this as any).os.isSignedIn) { | ||||
| 			this.connection = (this as any).os.stream.getConnection(); | ||||
| 			this.connectionId = (this as any).os.stream.use(); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.capture(true); | ||||
| 
 | ||||
|  | @ -152,6 +168,7 @@ export default Vue.extend({ | |||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeDestroy() { | ||||
| 		this.decapture(true); | ||||
| 
 | ||||
|  | @ -160,21 +177,7 @@ export default Vue.extend({ | |||
| 			(this as any).os.stream.dispose(this.connectionId); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		post: { | ||||
| 			handler(newPost, oldPost) { | ||||
| 				if (!oldPost || newPost.text !== oldPost.text) { | ||||
| 					this.$nextTick(() => { | ||||
| 						const elements = this.$refs.text.$el.getElementsByTagName('a'); | ||||
| 
 | ||||
| 						this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) | ||||
| 							.map(({ href }) => href); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			immediate: true | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		capture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
|  | @ -396,7 +399,7 @@ export default Vue.extend({ | |||
| 					font-size 1.1em | ||||
| 					color #717171 | ||||
| 
 | ||||
| 					>>> blockquote | ||||
| 					>>> .quote | ||||
| 						margin 8px | ||||
| 						padding 6px 12px | ||||
| 						color #aaa | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <div class="mk-sub-post-content"> | ||||
| 	<div class="body"> | ||||
| 		<a class="reply" v-if="post.replyId">%fa:reply%</a> | ||||
| 		<mk-post-html v-if="post.text" :ast="post.text" :i="os.i"/> | ||||
| 		<mk-post-html v-if="post.text" :text="post.text" :i="os.i"/> | ||||
| 		<a class="rp" v-if="post.repostId">RP: ...</a> | ||||
| 	</div> | ||||
| 	<details v-if="post.media.length > 0"> | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ const elements = [ | |||
| 	require('./elements/emoji') | ||||
| ]; | ||||
| 
 | ||||
| export default (source: string) => { | ||||
| export default (source: string): any[] => { | ||||
| 
 | ||||
| 	if (source == '') { | ||||
| 		return null; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue