Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
		
						commit
						c9e8399e0e
					
				
					 8 changed files with 64 additions and 39 deletions
				
			
		
							
								
								
									
										4
									
								
								.github/misskey/test.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/misskey/test.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -4,12 +4,12 @@ url: 'http://misskey.local' | ||||||
| port: 61812 | port: 61812 | ||||||
| 
 | 
 | ||||||
| db: | db: | ||||||
|   host: localhost |   host: 127.0.0.1 | ||||||
|   port: 54312 |   port: 54312 | ||||||
|   db: test-misskey |   db: test-misskey | ||||||
|   user: postgres |   user: postgres | ||||||
|   pass: '' |   pass: '' | ||||||
| redis: | redis: | ||||||
|   host: localhost |   host: 127.0.0.1 | ||||||
|   port: 56312 |   port: 56312 | ||||||
| id: aid | id: aid | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| 	<div class="bqxuuuey"> | 	<div class="bqxuuuey"> | ||||||
| 		<div class="reaction"> | 		<div class="reaction"> | ||||||
| 			<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> | 			<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> | ||||||
| 			<div class="name">{{ reaction.replace('@.', '') }}</div> | 			<div class="name">{{ getReactionName(reaction) }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="users"> | 		<div class="users"> | ||||||
| 			<div v-for="u in users" :key="u.id" class="user"> | 			<div v-for="u in users" :key="u.id" class="user"> | ||||||
|  | @ -20,6 +20,7 @@ | ||||||
| import { } from 'vue'; | import { } from 'vue'; | ||||||
| import MkTooltip from './MkTooltip.vue'; | import MkTooltip from './MkTooltip.vue'; | ||||||
| import XReactionIcon from '@/components/MkReactionIcon.vue'; | import XReactionIcon from '@/components/MkReactionIcon.vue'; | ||||||
|  | import { getEmojiName } from '@/scripts/emojilist'; | ||||||
| 
 | 
 | ||||||
| defineProps<{ | defineProps<{ | ||||||
| 	showing: boolean; | 	showing: boolean; | ||||||
|  | @ -33,6 +34,14 @@ defineProps<{ | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'closed'): void; | 	(ev: 'closed'): void; | ||||||
| }>(); | }>(); | ||||||
|  | 
 | ||||||
|  | function getReactionName(reaction: string): string { | ||||||
|  | 	const trimLocal = reaction.replace('@.', ''); | ||||||
|  | 	if (trimLocal.startsWith(':')) { | ||||||
|  | 		return trimLocal; | ||||||
|  | 	} | ||||||
|  | 	return getEmojiName(reaction) ?? reaction; | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|  |  | ||||||
|  | @ -1,17 +1,18 @@ | ||||||
| <template> | <template> | ||||||
| <img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/> | <img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/> | ||||||
| <img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt" decoding="async"/> | <img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" decoding="async" @pointerenter="computeTitle"/> | ||||||
| <span v-else-if="char && useOsNativeEmojis">{{ char }}</span> | <span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> | ||||||
| <span v-else>{{ emoji }}</span> | <span v-else>{{ emoji }}</span> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, ref, watch } from 'vue'; | import { computed } from 'vue'; | ||||||
| import { CustomEmoji } from 'misskey-js/built/entities'; | import { CustomEmoji } from 'misskey-js/built/entities'; | ||||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||||
| import { char2filePath } from '@/scripts/twemoji-base'; | import { char2filePath } from '@/scripts/twemoji-base'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
| import { instance } from '@/instance'; | import { instance } from '@/instance'; | ||||||
|  | import { getEmojiName } from '@/scripts/emojilist'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	emoji: string; | 	emoji: string; | ||||||
|  | @ -22,20 +23,28 @@ const props = defineProps<{ | ||||||
| }>(); | }>(); | ||||||
| 
 | 
 | ||||||
| const isCustom = computed(() => props.emoji.startsWith(':')); | const isCustom = computed(() => props.emoji.startsWith(':')); | ||||||
| const char = computed(() => isCustom.value ? null : props.emoji); | const char = computed(() => isCustom.value ? undefined : props.emoji); | ||||||
| const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !props.isReaction); | const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !props.isReaction); | ||||||
| const ce = computed(() => props.customEmojis ?? instance.emojis ?? []); | const ce = computed(() => props.customEmojis ?? instance.emojis ?? []); | ||||||
| const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null); | const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : undefined); | ||||||
| const url = computed(() => { | const url = computed(() => { | ||||||
| 	if (char.value) { | 	if (char.value) { | ||||||
| 		return char2filePath(char.value); | 		return char2filePath(char.value); | ||||||
| 	} else { | 	} else { | ||||||
|  | 		const rawUrl = (customEmoji.value as CustomEmoji).url; | ||||||
| 		return defaultStore.state.disableShowingAnimatedImages | 		return defaultStore.state.disableShowingAnimatedImages | ||||||
| 			? getStaticImageUrl(customEmoji.value.url) | 			? getStaticImageUrl(rawUrl) | ||||||
| 			: customEmoji.value.url; | 			: rawUrl; | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value); | const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value); | ||||||
|  | 
 | ||||||
|  | function computeTitle(event: PointerEvent): void { | ||||||
|  | 	const title = customEmoji.value | ||||||
|  | 		? `:${customEmoji.value.name}:` | ||||||
|  | 		: (getEmojiName(char.value as string) ?? char.value as string); | ||||||
|  | 	(event.target as HTMLElement).title = title; | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-if="!narrow || hideTitle" class="tabs"> | 		<div v-if="!narrow || hideTitle" class="tabs"> | ||||||
| 			<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = el" v-tooltip.noDelay="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)"> | 			<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = (el as HTMLElement)" v-tooltip.noDelay="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)"> | ||||||
| 				<i v-if="tab.icon" class="icon" :class="tab.icon"></i> | 				<i v-if="tab.icon" class="icon" :class="tab.icon"></i> | ||||||
| 				<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> | 				<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> | ||||||
| 			</button> | 			</button> | ||||||
|  | @ -37,34 +37,36 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, onMounted, onUnmounted, ref, inject, watch, shallowReactive, nextTick, reactive } from 'vue'; | import { onMounted, onUnmounted, ref, inject, watch, nextTick } from 'vue'; | ||||||
| import tinycolor from 'tinycolor2'; | import tinycolor from 'tinycolor2'; | ||||||
| import { popupMenu } from '@/os'; | import { popupMenu } from '@/os'; | ||||||
| import { scrollToTop } from '@/scripts/scroll'; | import { scrollToTop } from '@/scripts/scroll'; | ||||||
| import { i18n } from '@/i18n'; |  | ||||||
| import { globalEvents } from '@/events'; | import { globalEvents } from '@/events'; | ||||||
| import { injectPageMetadata } from '@/scripts/page-metadata'; | import { injectPageMetadata } from '@/scripts/page-metadata'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
| 
 | 
 | ||||||
| type Tab = { | type Tab = { | ||||||
| 	key?: string | null; | 	key: string; | ||||||
| 	title: string; | 	title: string; | ||||||
| 	icon?: string; | 	icon?: string; | ||||||
| 	iconOnly?: boolean; | 	iconOnly?: boolean; | ||||||
| 	onClick?: (ev: MouseEvent) => void; | 	onClick?: (ev: MouseEvent) => void; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	tabs?: Tab[]; | 	tabs?: Tab[]; | ||||||
| 	tab?: string; | 	tab?: string; | ||||||
| 	actions?: { | 	actions?: { | ||||||
| 		text: string; | 		text: string; | ||||||
| 		icon: string; | 		icon: string; | ||||||
|  | 		highlighted?: boolean; | ||||||
| 		handler: (ev: MouseEvent) => void; | 		handler: (ev: MouseEvent) => void; | ||||||
| 	}[]; | 	}[]; | ||||||
| 	thin?: boolean; | 	thin?: boolean; | ||||||
| 	displayMyAvatar?: boolean; | 	displayMyAvatar?: boolean; | ||||||
| }>(); | }>(), { | ||||||
|  | 	tabs: () => ([] as Tab[]) | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'update:tab', key: string); | 	(ev: 'update:tab', key: string); | ||||||
|  | @ -75,13 +77,12 @@ const metadata = injectPageMetadata(); | ||||||
| const hideTitle = inject('shouldOmitHeaderTitle', false); | const hideTitle = inject('shouldOmitHeaderTitle', false); | ||||||
| const thin_ = props.thin || inject('shouldHeaderThin', false); | const thin_ = props.thin || inject('shouldHeaderThin', false); | ||||||
| 
 | 
 | ||||||
| const el = $ref<HTMLElement | null>(null); | const el = $ref<HTMLElement | undefined>(undefined); | ||||||
| const tabRefs = {}; | const tabRefs: Record<string, HTMLElement | null> = {}; | ||||||
| const tabHighlightEl = $ref<HTMLElement | null>(null); | const tabHighlightEl = $ref<HTMLElement | null>(null); | ||||||
| const bg = ref(null); | const bg = ref<string | undefined>(undefined); | ||||||
| let narrow = $ref(false); | let narrow = $ref(false); | ||||||
| const height = ref(0); | const hasTabs = $computed(() => props.tabs.length > 0); | ||||||
| const hasTabs = $computed(() => props.tabs && props.tabs.length > 0); |  | ||||||
| const hasActions = $computed(() => props.actions && props.actions.length > 0); | const hasActions = $computed(() => props.actions && props.actions.length > 0); | ||||||
| const show = $computed(() => { | const show = $computed(() => { | ||||||
| 	return !hideTitle || hasTabs || hasActions; | 	return !hideTitle || hasTabs || hasActions; | ||||||
|  | @ -100,7 +101,7 @@ const showTabsPopup = (ev: MouseEvent) => { | ||||||
| 			onTabClick(tab, ev); | 			onTabClick(tab, ev); | ||||||
| 		}, | 		}, | ||||||
| 	})); | 	})); | ||||||
| 	popupMenu(menu, ev.currentTarget ?? ev.target); | 	popupMenu(menu, ev.currentTarget! as HTMLElement); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const preventDrag = (ev: TouchEvent) => { | const preventDrag = (ev: TouchEvent) => { | ||||||
|  | @ -108,7 +109,9 @@ const preventDrag = (ev: TouchEvent) => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const onClick = () => { | const onClick = () => { | ||||||
| 	scrollToTop(el, { behavior: 'smooth' }); | 	if (el) { | ||||||
|  | 		scrollToTop(el as HTMLElement, { behavior: 'smooth' }); | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function onTabMousedown(tab: Tab, ev: MouseEvent): void { | function onTabMousedown(tab: Tab, ev: MouseEvent): void { | ||||||
|  | @ -144,8 +147,8 @@ onMounted(() => { | ||||||
| 
 | 
 | ||||||
| 	watch(() => [props.tab, props.tabs], () => { | 	watch(() => [props.tab, props.tabs], () => { | ||||||
| 		nextTick(() => { | 		nextTick(() => { | ||||||
| 			const tabEl = tabRefs[props.tab]; | 			const tabEl = props.tab ? tabRefs[props.tab] : undefined; | ||||||
| 			if (tabEl && tabHighlightEl) { | 			if (tabEl && tabHighlightEl && tabEl.parentElement) { | ||||||
| 				// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある | 				// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある | ||||||
| 				// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4 | 				// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4 | ||||||
| 				const parentRect = tabEl.parentElement.getBoundingClientRect(); | 				const parentRect = tabEl.parentElement.getBoundingClientRect(); | ||||||
|  | @ -161,11 +164,11 @@ onMounted(() => { | ||||||
| 	if (el && el.parentElement) { | 	if (el && el.parentElement) { | ||||||
| 		narrow = el.parentElement.offsetWidth < 500; | 		narrow = el.parentElement.offsetWidth < 500; | ||||||
| 		ro = new ResizeObserver((entries, observer) => { | 		ro = new ResizeObserver((entries, observer) => { | ||||||
| 			if (el.parentElement && document.body.contains(el)) { | 			if (el.parentElement && document.body.contains(el as HTMLElement)) { | ||||||
| 				narrow = el.parentElement.offsetWidth < 500; | 				narrow = el.parentElement.offsetWidth < 500; | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 		ro.observe(el.parentElement); | 		ro.observe(el.parentElement as HTMLElement); | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -96,9 +96,9 @@ onMounted(async () => { | ||||||
| 	&:global { | 	&:global { | ||||||
| 		> .pies { | 		> .pies { | ||||||
| 			display: grid; | 			display: grid; | ||||||
| 			grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | 			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); | ||||||
| 			grid-gap: 16px; | 			grid-gap: 12px; | ||||||
| 			margin-bottom: 16px; | 			margin-bottom: 12px; | ||||||
| 
 | 
 | ||||||
| 			> .pie { | 			> .pie { | ||||||
| 				position: relative; | 				position: relative; | ||||||
|  | @ -126,8 +126,8 @@ onMounted(async () => { | ||||||
| 
 | 
 | ||||||
| 		> .items { | 		> .items { | ||||||
| 			display: grid; | 			display: grid; | ||||||
| 			grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | 			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); | ||||||
| 			grid-gap: 16px; | 			grid-gap: 12px; | ||||||
| 
 | 
 | ||||||
| 			> .item { | 			> .item { | ||||||
| 				display: flex; | 				display: flex; | ||||||
|  | @ -160,10 +160,10 @@ onMounted(async () => { | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				> .body { | 				> .body { | ||||||
| 					padding: 4px 0; | 					padding: 2px 0; | ||||||
| 
 | 
 | ||||||
| 					> .value { | 					> .value { | ||||||
| 						font-size: 1.3em; | 						font-size: 1.25em; | ||||||
| 						font-weight: bold; | 						font-weight: bold; | ||||||
| 
 | 
 | ||||||
| 						> .diff { | 						> .diff { | ||||||
|  |  | ||||||
|  | @ -109,7 +109,7 @@ onUnmounted(() => { | ||||||
| 		> .charts { | 		> .charts { | ||||||
| 			display: grid; | 			display: grid; | ||||||
| 			grid-template-columns: 1fr 1fr; | 			grid-template-columns: 1fr 1fr; | ||||||
| 			gap: 16px; | 			gap: 12px; | ||||||
| 
 | 
 | ||||||
| 			> .chart { | 			> .chart { | ||||||
| 				min-width: 0; | 				min-width: 0; | ||||||
|  |  | ||||||
|  | @ -81,8 +81,8 @@ onMounted(async () => { | ||||||
| <style lang="scss" module> | <style lang="scss" module> | ||||||
| .root { | .root { | ||||||
| 	display: grid; | 	display: grid; | ||||||
| 	grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | 	grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); | ||||||
| 	grid-gap: 16px; | 	grid-gap: 12px; | ||||||
| 
 | 
 | ||||||
| 	&:global { | 	&:global { | ||||||
| 		> .item { | 		> .item { | ||||||
|  | @ -130,10 +130,10 @@ onMounted(async () => { | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			> .body { | 			> .body { | ||||||
| 				padding: 4px 0; | 				padding: 2px 0; | ||||||
| 
 | 
 | ||||||
| 				> .value { | 				> .value { | ||||||
| 					font-size: 1.3em; | 					font-size: 1.25em; | ||||||
| 					font-weight: bold; | 					font-weight: bold; | ||||||
| 
 | 
 | ||||||
| 					> .diff { | 					> .diff { | ||||||
|  |  | ||||||
|  | @ -11,3 +11,7 @@ export type UnicodeEmojiDef = { | ||||||
| import _emojilist from '../emojilist.json'; | import _emojilist from '../emojilist.json'; | ||||||
| 
 | 
 | ||||||
| export const emojilist = _emojilist as UnicodeEmojiDef[]; | export const emojilist = _emojilist as UnicodeEmojiDef[]; | ||||||
|  | 
 | ||||||
|  | export function getEmojiName(char: string): string | undefined { | ||||||
|  | 	return emojilist.find(emo => emo.char === char)?.name; | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue