Merge branch 'develop' into refactor-ui
This commit is contained in:
		
						commit
						2e898c173c
					
				
					 7 changed files with 204 additions and 35 deletions
				
			
		|  | @ -10,11 +10,15 @@ | ||||||
| ## 12.x.x (unreleased) | ## 12.x.x (unreleased) | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
| - アニメーションを減らす設定をメニューのアニメーションにも適用するように | - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように | ||||||
|  | - クライアント: MFM関数構文のサジェストを実装 | ||||||
|  | - ActivityPub: HTML -> MFMの変換を強化 | ||||||
| 
 | 
 | ||||||
| ### Bugfixes | ### Bugfixes | ||||||
| - Fix createDeleteAccountJob | - Fix createDeleteAccountJob | ||||||
| - admin inbox queue does not show individual jobs | - admin inbox queue does not show individual jobs | ||||||
|  | - クライアント: ヘッダーのタブが折り返される問題を修正 | ||||||
|  | - クライアント: ヘッダーにタブが表示されている状態でタイトルをクリックしたときにタブ選択が表示されるのを修正 | ||||||
| 
 | 
 | ||||||
| ## 12.91.0 (2021/09/22) | ## 12.91.0 (2021/09/22) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,12 +10,12 @@ | ||||||
| 		</li> | 		</li> | ||||||
| 		<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li> | 		<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li> | ||||||
| 	</ol> | 	</ol> | ||||||
| 	<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0"> | 	<ol class="hashtags" ref="suggests" v-else-if="hashtags.length > 0"> | ||||||
| 		<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1"> | 		<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1"> | ||||||
| 			<span class="name">{{ hashtag }}</span> | 			<span class="name">{{ hashtag }}</span> | ||||||
| 		</li> | 		</li> | ||||||
| 	</ol> | 	</ol> | ||||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | 	<ol class="emojis" ref="suggests" v-else-if="emojis.length > 0"> | ||||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||||
| 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> | 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> | ||||||
| 			<span class="emoji" v-else-if="!$store.state.useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | 			<span class="emoji" v-else-if="!$store.state.useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||||
|  | @ -24,6 +24,11 @@ | ||||||
| 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | ||||||
| 		</li> | 		</li> | ||||||
| 	</ol> | 	</ol> | ||||||
|  | 	<ol class="mfmTags" ref="suggests" v-else-if="mfmTags.length > 0"> | ||||||
|  | 		<li v-for="tag in mfmTags" @click="complete(type, tag)" @keydown="onKeydown" tabindex="-1"> | ||||||
|  | 			<span class="tag">{{ tag }}</span> | ||||||
|  | 		</li> | ||||||
|  | 	</ol> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -106,6 +111,8 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); | ||||||
| const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); | const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); | ||||||
| //#endregion | //#endregion | ||||||
| 
 | 
 | ||||||
|  | const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle']; | ||||||
|  | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	props: { | 	props: { | ||||||
| 		type: { | 		type: { | ||||||
|  | @ -137,11 +144,6 @@ export default defineComponent({ | ||||||
| 			type: Number, | 			type: Number, | ||||||
| 			required: true, | 			required: true, | ||||||
| 		}, | 		}, | ||||||
| 
 |  | ||||||
| 		showing: { |  | ||||||
| 			type: Boolean, |  | ||||||
| 			required: true |  | ||||||
| 		}, |  | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	emits: ['done', 'closed'], | 	emits: ['done', 'closed'], | ||||||
|  | @ -154,18 +156,11 @@ export default defineComponent({ | ||||||
| 			hashtags: [], | 			hashtags: [], | ||||||
| 			emojis: [], | 			emojis: [], | ||||||
| 			items: [], | 			items: [], | ||||||
|  | 			mfmTags: [], | ||||||
| 			select: -1, | 			select: -1, | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	watch: { |  | ||||||
| 		showing() { |  | ||||||
| 			if (!this.showing) { |  | ||||||
| 				this.$emit('closed'); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	updated() { | 	updated() { | ||||||
| 		this.setPosition(); | 		this.setPosition(); | ||||||
| 		this.items = (this.$refs.suggests as Element | undefined)?.children || []; | 		this.items = (this.$refs.suggests as Element | undefined)?.children || []; | ||||||
|  | @ -236,7 +231,7 @@ export default defineComponent({ | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (this.type == 'user') { | 			if (this.type === 'user') { | ||||||
| 				if (this.q == null) { | 				if (this.q == null) { | ||||||
| 					this.users = []; | 					this.users = []; | ||||||
| 					this.fetching = false; | 					this.fetching = false; | ||||||
|  | @ -262,7 +257,7 @@ export default defineComponent({ | ||||||
| 						sessionStorage.setItem(cacheKey, JSON.stringify(users)); | 						sessionStorage.setItem(cacheKey, JSON.stringify(users)); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
| 			} else if (this.type == 'hashtag') { | 			} else if (this.type === 'hashtag') { | ||||||
| 				if (this.q == null || this.q == '') { | 				if (this.q == null || this.q == '') { | ||||||
| 					this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); | 					this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); | ||||||
| 					this.fetching = false; | 					this.fetching = false; | ||||||
|  | @ -286,7 +281,7 @@ export default defineComponent({ | ||||||
| 						}); | 						}); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} else if (this.type == 'emoji') { | 			} else if (this.type === 'emoji') { | ||||||
| 				if (this.q == null || this.q == '') { | 				if (this.q == null || this.q == '') { | ||||||
| 					// 最近使った絵文字をサジェスト | 					// 最近使った絵文字をサジェスト | ||||||
| 					this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); | 					this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); | ||||||
|  | @ -314,6 +309,13 @@ export default defineComponent({ | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				this.emojis = matched; | 				this.emojis = matched; | ||||||
|  | 			} else if (this.type === 'mfmTag') { | ||||||
|  | 				if (this.q == null || this.q == '') { | ||||||
|  | 					this.mfmTags = MFM_TAGS; | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | @ -490,5 +492,11 @@ export default defineComponent({ | ||||||
| 			margin: 0 0 0 8px; | 			margin: 0 0 0 8px; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	> .mfmTags > li { | ||||||
|  | 
 | ||||||
|  | 		.name { | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -5,9 +5,11 @@ | ||||||
| 			<template #prefix><i class="fas fa-search"></i></template> | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 
 | 
 | ||||||
|  | 		<!-- たくさんあると邪魔 | ||||||
| 		<div class="tags"> | 		<div class="tags"> | ||||||
| 			<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span> | 			<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		--> | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<MkFolder class="emojis" v-if="searchEmojis"> | 	<MkFolder class="emojis" v-if="searchEmojis"> | ||||||
|  |  | ||||||
|  | @ -7,9 +7,9 @@ export class Autocomplete { | ||||||
| 	private suggestion: { | 	private suggestion: { | ||||||
| 		x: Ref<number>; | 		x: Ref<number>; | ||||||
| 		y: Ref<number>; | 		y: Ref<number>; | ||||||
| 		q: Ref<string>; | 		q: Ref<string | null>; | ||||||
| 		close: Function; | 		close: Function; | ||||||
| 	}; | 	} | null; | ||||||
| 	private textarea: any; | 	private textarea: any; | ||||||
| 	private vm: any; | 	private vm: any; | ||||||
| 	private currentType: string; | 	private currentType: string; | ||||||
|  | @ -70,11 +70,13 @@ export class Autocomplete { | ||||||
| 		const mentionIndex = text.lastIndexOf('@'); | 		const mentionIndex = text.lastIndexOf('@'); | ||||||
| 		const hashtagIndex = text.lastIndexOf('#'); | 		const hashtagIndex = text.lastIndexOf('#'); | ||||||
| 		const emojiIndex = text.lastIndexOf(':'); | 		const emojiIndex = text.lastIndexOf(':'); | ||||||
|  | 		const mfmTagIndex = text.lastIndexOf('$'); | ||||||
| 
 | 
 | ||||||
| 		const max = Math.max( | 		const max = Math.max( | ||||||
| 			mentionIndex, | 			mentionIndex, | ||||||
| 			hashtagIndex, | 			hashtagIndex, | ||||||
| 			emojiIndex); | 			emojiIndex, | ||||||
|  | 			mfmTagIndex); | ||||||
| 
 | 
 | ||||||
| 		if (max == -1) { | 		if (max == -1) { | ||||||
| 			this.close(); | 			this.close(); | ||||||
|  | @ -83,6 +85,7 @@ export class Autocomplete { | ||||||
| 
 | 
 | ||||||
| 		const isMention = mentionIndex != -1; | 		const isMention = mentionIndex != -1; | ||||||
| 		const isHashtag = hashtagIndex != -1; | 		const isHashtag = hashtagIndex != -1; | ||||||
|  | 		const isMfmTag = mfmTagIndex != -1; | ||||||
| 		const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); | 		const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); | ||||||
| 
 | 
 | ||||||
| 		let opened = false; | 		let opened = false; | ||||||
|  | @ -114,6 +117,14 @@ export class Autocomplete { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if (isMfmTag && !opened) { | ||||||
|  | 			const mfmTag = text.substr(mfmTagIndex + 1); | ||||||
|  | 			if (!mfmTag.includes(' ')) { | ||||||
|  | 				this.open('mfmTag', mfmTag.replace('[', '')); | ||||||
|  | 				opened = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if (!opened) { | 		if (!opened) { | ||||||
| 			this.close(); | 			this.close(); | ||||||
| 		} | 		} | ||||||
|  | @ -122,7 +133,7 @@ export class Autocomplete { | ||||||
| 	/** | 	/** | ||||||
| 	 * サジェストを提示します。 | 	 * サジェストを提示します。 | ||||||
| 	 */ | 	 */ | ||||||
| 	private async open(type: string, q: string) { | 	private async open(type: string, q: string | null) { | ||||||
| 		if (type != this.currentType) { | 		if (type != this.currentType) { | ||||||
| 			this.close(); | 			this.close(); | ||||||
| 		} | 		} | ||||||
|  | @ -244,6 +255,22 @@ export class Autocomplete { | ||||||
| 				const pos = trimmedBefore.length + value.length; | 				const pos = trimmedBefore.length + value.length; | ||||||
| 				this.textarea.setSelectionRange(pos, pos); | 				this.textarea.setSelectionRange(pos, pos); | ||||||
| 			}); | 			}); | ||||||
|  | 		} else if (type == 'mfmTag') { | ||||||
|  | 			const source = this.text; | ||||||
|  | 
 | ||||||
|  | 			const before = source.substr(0, caret); | ||||||
|  | 			const trimmedBefore = before.substring(0, before.lastIndexOf('$')); | ||||||
|  | 			const after = source.substr(caret); | ||||||
|  | 
 | ||||||
|  | 			// 挿入
 | ||||||
|  | 			this.text = `${trimmedBefore}$[${value} ]${after}`; | ||||||
|  | 
 | ||||||
|  | 			// キャレットを戻す
 | ||||||
|  | 			this.vm.$nextTick(() => { | ||||||
|  | 				this.textarea.focus(); | ||||||
|  | 				const pos = trimmedBefore.length + (value.length + 3); | ||||||
|  | 				this.textarea.setSelectionRange(pos, pos); | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -141,6 +141,7 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| 		showTabsPopup(ev) { | 		showTabsPopup(ev) { | ||||||
| 			if (!this.hasTabs) return; | 			if (!this.hasTabs) return; | ||||||
|  | 			if (!this.narrow) return; | ||||||
| 			ev.preventDefault(); | 			ev.preventDefault(); | ||||||
| 			ev.stopPropagation(); | 			ev.stopPropagation(); | ||||||
| 			const menu = this.info.tabs.map(tab => ({ | 			const menu = this.info.tabs.map(tab => ({ | ||||||
|  | @ -218,6 +219,7 @@ export default defineComponent({ | ||||||
| 		white-space: nowrap; | 		white-space: nowrap; | ||||||
| 		text-align: left; | 		text-align: left; | ||||||
| 		font-weight: bold; | 		font-weight: bold; | ||||||
|  | 		flex-shrink: 0; | ||||||
| 
 | 
 | ||||||
| 		> .avatar { | 		> .avatar { | ||||||
| 			$size: 32px; | 			$size: 32px; | ||||||
|  | @ -263,6 +265,8 @@ export default defineComponent({ | ||||||
| 	> .tabs { | 	> .tabs { | ||||||
| 		margin-left: 16px; | 		margin-left: 16px; | ||||||
| 		font-size: 0.8em; | 		font-size: 0.8em; | ||||||
|  | 		overflow: auto; | ||||||
|  | 		white-space: nowrap; | ||||||
| 
 | 
 | ||||||
| 		> .tab { | 		> .tab { | ||||||
| 			display: inline-block; | 			display: inline-block; | ||||||
|  |  | ||||||
|  | @ -5,7 +5,9 @@ import { URL } from 'url'; | ||||||
| const urlRegex     = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; | const urlRegex     = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; | ||||||
| const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; | const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; | ||||||
| 
 | 
 | ||||||
| export function fromHtml(html: string, hashtagNames?: string[]): string { | export function fromHtml(html: string, hashtagNames?: string[]): string | null { | ||||||
|  | 	if (html == null) return null; | ||||||
|  | 
 | ||||||
| 	const dom = parse5.parseFragment(html); | 	const dom = parse5.parseFragment(html); | ||||||
| 
 | 
 | ||||||
| 	let text = ''; | 	let text = ''; | ||||||
|  | @ -19,6 +21,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { | ||||||
| 	function getText(node: parse5.Node): string { | 	function getText(node: parse5.Node): string { | ||||||
| 		if (treeAdapter.isTextNode(node)) return node.value; | 		if (treeAdapter.isTextNode(node)) return node.value; | ||||||
| 		if (!treeAdapter.isElementNode(node)) return ''; | 		if (!treeAdapter.isElementNode(node)) return ''; | ||||||
|  | 		if (node.nodeName === 'br') return '\n'; | ||||||
| 
 | 
 | ||||||
| 		if (node.childNodes) { | 		if (node.childNodes) { | ||||||
| 			return node.childNodes.map(n => getText(n)).join(''); | 			return node.childNodes.map(n => getText(n)).join(''); | ||||||
|  | @ -27,6 +30,14 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { | ||||||
| 		return ''; | 		return ''; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	function appendChildren(childNodes: parse5.ChildNode[]): void { | ||||||
|  | 		if (childNodes) { | ||||||
|  | 			for (const n of childNodes) { | ||||||
|  | 				analyze(n); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	function analyze(node: parse5.Node) { | 	function analyze(node: parse5.Node) { | ||||||
| 		if (treeAdapter.isTextNode(node)) { | 		if (treeAdapter.isTextNode(node)) { | ||||||
| 			text += node.value; | 			text += node.value; | ||||||
|  | @ -42,6 +53,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'a': | 			case 'a': | ||||||
|  | 			{ | ||||||
| 				const txt = getText(node); | 				const txt = getText(node); | ||||||
| 				const rel = node.attrs.find(x => x.name === 'rel'); | 				const rel = node.attrs.find(x => x.name === 'rel'); | ||||||
| 				const href = node.attrs.find(x => x.name === 'href'); | 				const href = node.attrs.find(x => x.name === 'href'); | ||||||
|  | @ -87,23 +99,111 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { | ||||||
| 					text += generateLink(); | 					text += generateLink(); | ||||||
| 				} | 				} | ||||||
| 				break; | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 'h1': | ||||||
|  | 			{ | ||||||
|  | 				text += '【'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '】\n'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 'b': | ||||||
|  | 			case 'strong': | ||||||
|  | 			{ | ||||||
|  | 				text += '**'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '**'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 'small': | ||||||
|  | 			{ | ||||||
|  | 				text += '<small>'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '</small>'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 's': | ||||||
|  | 			case 'del': | ||||||
|  | 			{ | ||||||
|  | 				text += '~~'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '~~'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 'i': | ||||||
|  | 			case 'em': | ||||||
|  | 			{ | ||||||
|  | 				text += '<i>'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '</i>'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// block code (<pre><code>)
 | ||||||
|  | 			case 'pre': { | ||||||
|  | 				if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') { | ||||||
|  | 					text += '```\n'; | ||||||
|  | 					text += getText(node.childNodes[0]); | ||||||
|  | 					text += '\n```\n'; | ||||||
|  | 				} else { | ||||||
|  | 					appendChildren(node.childNodes); | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// inline code (<code>)
 | ||||||
|  | 			case 'code': { | ||||||
|  | 				text += '`'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				text += '`'; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			case 'blockquote': { | ||||||
|  | 				const t = getText(node); | ||||||
|  | 				if (t) { | ||||||
|  | 					text += '> '; | ||||||
|  | 					text += t.split('\n').join(`\n> `); | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			case 'p': | 			case 'p': | ||||||
|  | 			case 'h2': | ||||||
|  | 			case 'h3': | ||||||
|  | 			case 'h4': | ||||||
|  | 			case 'h5': | ||||||
|  | 			case 'h6': | ||||||
|  | 			{ | ||||||
| 				text += '\n\n'; | 				text += '\n\n'; | ||||||
| 				if (node.childNodes) { | 				appendChildren(node.childNodes); | ||||||
| 					for (const n of node.childNodes) { |  | ||||||
| 						analyze(n); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				break; | 				break; | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			default: | 			// other block elements
 | ||||||
| 				if (node.childNodes) { | 			case 'div': | ||||||
| 					for (const n of node.childNodes) { | 			case 'header': | ||||||
| 						analyze(n); | 			case 'footer': | ||||||
| 					} | 			case 'article': | ||||||
| 				} | 			case 'li': | ||||||
|  | 			case 'dt': | ||||||
|  | 			case 'dd': | ||||||
|  | 			{ | ||||||
|  | 				text += '\n'; | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
| 				break; | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			default:	// includes inline elements
 | ||||||
|  | 			{ | ||||||
|  | 				appendChildren(node.childNodes); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								test/mfm.ts
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								test/mfm.ts
									
										
									
									
									
								
							|  | @ -19,6 +19,30 @@ describe('toHtml', () => { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe('fromHtml', () => { | describe('fromHtml', () => { | ||||||
|  | 	it('p', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<p>a</p><p>b</p>'), 'a\n\nb'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it('block element', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<div>a</div><div>b</div>'), 'a\nb'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it('inline element', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<ul><li>a</li><li>b</li></ul>'), 'a\nb'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it('block code', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<pre><code>a\nb</code></pre>'), '```\na\nb\n```'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it('inline code', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<code>a</code>'), '`a`'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it('quote', () => { | ||||||
|  | 		assert.deepStrictEqual(fromHtml('<blockquote>a\nb</blockquote>'), '> a\n> b'); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	it('br', () => { | 	it('br', () => { | ||||||
| 		assert.deepStrictEqual(fromHtml('<p>abc<br><br/>d</p>'), 'abc\n\nd'); | 		assert.deepStrictEqual(fromHtml('<p>abc<br><br/>d</p>'), 'abc\n\nd'); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue