diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-07-27 13:00:14 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-27 13:00:14 +0900 |
| commit | a8d45d4b0d24e0c422d4e6d8feab57035239db56 (patch) | |
| tree | 7fd6414990788c281997c75882961b8364e337ab /packages/frontend/src | |
| parent | Merge pull request #11338 from misskey-dev/develop (diff) | |
| parent | chore: update pnpm to 8.6.10 (diff) | |
| download | misskey-a8d45d4b0d24e0c422d4e6d8feab57035239db56.tar.gz misskey-a8d45d4b0d24e0c422d4e6d8feab57035239db56.tar.bz2 misskey-a8d45d4b0d24e0c422d4e6d8feab57035239db56.zip | |
Merge pull request #11384 from misskey-dev/develop
Release: 13.14.2
Diffstat (limited to 'packages/frontend/src')
18 files changed, 149 insertions, 90 deletions
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 4e36defb7c..6dcc890cd3 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -108,8 +108,7 @@ function waitForDecode() { .then(() => { loaded = true; }, error => { - console.error('Error occurred during decoding image', img.value, error); - throw Error(error); + console.log('Error occurred during decoding image', img.value, error); }); } else { loaded = false; diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index be0aed6524..0cdccfb169 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -1,5 +1,5 @@ <template> -<div> +<div ref="root"> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container"> <div @@ -22,8 +22,43 @@ </div> </template> +<script lang="ts"> +/** + * アスペクト比算出のためにHTMLElement.clientWidthを使うが、 + * 大変重たいのでコンテナ要素とメディアリスト幅のペアをキャッシュする + * (タイムラインごとにスクロールコンテナが存在する前提だが……) + */ +const widthCache = new Map<Element, number>(); + +/** + * コンテナ要素がリサイズされたらキャッシュを削除する + */ +const ro = new ResizeObserver(entries => { + for (const entry of entries) { + widthCache.delete(entry.target); + } +}); + +async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLElement, count = 0) { + if (_DEV_) console.log('getClientWidthWithCache', { targetEl, containerEl, count, cache: widthCache.get(containerEl) }); + if (widthCache.has(containerEl)) return widthCache.get(containerEl)!; + + const width = targetEl.clientWidth; + + if (count <= 10 && width < 64) { + // widthが64未満はおかしいのでリトライする + await new Promise(resolve => setTimeout(resolve, 50)); + return getClientWidthWithCache(targetEl, containerEl, count + 1); + } + + widthCache.set(containerEl, width); + ro.observe(containerEl); + return width; +} +</script> + <script lang="ts" setup> -import { onMounted, watch, shallowRef } from 'vue'; +import { onMounted, onUnmounted, shallowRef } from 'vue'; import * as misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; @@ -34,19 +69,33 @@ import XVideo from '@/components/MkMediaVideo.vue'; import * as os from '@/os'; import { FILE_TYPE_BROWSERSAFE } from '@/const'; import { defaultStore } from '@/store'; +import { getScrollContainer, getBodyScrollHeight } from '@/scripts/scroll'; const props = defineProps<{ mediaList: misskey.entities.DriveFile[]; raw?: boolean; }>(); +const root = shallowRef<HTMLDivElement>(); +const container = shallowRef<HTMLElement | null | undefined>(undefined); const gallery = shallowRef<HTMLDivElement>(); const pswpZIndex = os.claimZIndex('middle'); document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); const count = $computed(() => props.mediaList.filter(media => previewable(media)).length); +let lightbox: PhotoSwipeLightbox | null; -function calcAspectRatio() { - if (!gallery.value) return; +const popstateHandler = (): void => { + if (lightbox.pswp && lightbox.pswp.isOpen === true) { + lightbox.pswp.close(); + } +}; + +/** + * アスペクト比をmediaListWithOneImageAppearanceに基づいていい感じに調整する + * aspect-ratioではなくheightを使う + */ +async function calcAspectRatio() { + if (!gallery.value || !root.value) return; let img = props.mediaList[0]; @@ -55,29 +104,47 @@ function calcAspectRatio() { return; } - // アスペクト比上限設定では、横長の場合は高さを縮小させる - const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`; + if (!container.value) container.value = getScrollContainer(root.value); + const width = container.value ? await getClientWidthWithCache(root.value, container.value) : root.value.clientWidth; + + const heightMin = (ratio: number) => { + const imgResizeRatio = width / img.properties.width; + const imgDrawHeight = img.properties.height * imgResizeRatio; + const maxHeight = width * ratio; + const height = Math.min(imgDrawHeight, maxHeight); + if (_DEV_) console.log('Image height calculated:', { width, properties: img.properties, imgResizeRatio, imgDrawHeight, maxHeight, height }); + return `${height}px`; + }; switch (defaultStore.state.mediaListWithOneImageAppearance) { case '16_9': - gallery.value.style.aspectRatio = ratioMax(16 / 9); + gallery.value.style.height = heightMin(9 / 16); break; case '1_1': - gallery.value.style.aspectRatio = ratioMax(1); + gallery.value.style.height = heightMin(1); break; case '2_3': - gallery.value.style.aspectRatio = ratioMax(2 / 3); + gallery.value.style.height = heightMin(3 / 2); break; - default: - gallery.value.style.aspectRatio = ''; + default: { + const maxHeight = Math.max(64, (container.value ? container.value.clientHeight : getBodyScrollHeight()) * 0.5 || 360); + if (width === 0 || !maxHeight) return; + const imgResizeRatio = width / img.properties.width; + const imgDrawHeight = img.properties.height * imgResizeRatio; + gallery.value.style.height = `${Math.max(64, Math.min(imgDrawHeight, maxHeight))}px`; + gallery.value.style.minHeight = 'initial'; + gallery.value.style.maxHeight = 'initial'; break; + } } -} -watch([defaultStore.reactiveState.mediaListWithOneImageAppearance, gallery], () => calcAspectRatio()); + gallery.value.style.aspectRatio = 'initial'; +} onMounted(() => { - const lightbox = new PhotoSwipeLightbox({ + calcAspectRatio(); + + lightbox = new PhotoSwipeLightbox({ dataSource: props.mediaList .filter(media => { if (media.type === 'image/svg+xml') return true; // svgのwebpublicはpngなのでtrue @@ -161,12 +228,7 @@ onMounted(() => { lightbox.init(); - window.addEventListener('popstate', () => { - if (lightbox.pswp && lightbox.pswp.isOpen === true) { - lightbox.pswp.close(); - return; - } - }); + window.addEventListener('popstate', popstateHandler); lightbox.on('beforeOpen', () => { history.pushState(null, '', '#pswp'); @@ -179,6 +241,12 @@ onMounted(() => { }); }); +onUnmounted(() => { + window.removeEventListener('popstate', popstateHandler); + lightbox?.destroy(); + lightbox = null; +}); + const previewable = (file: misskey.entities.DriveFile): boolean => { if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 @@ -203,7 +271,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => { &.n1 { grid-template-rows: 1fr; - // default (expand) + // default but fallback (expand) min-height: 64px; max-height: clamp( 64px, @@ -212,20 +280,20 @@ const previewable = (file: misskey.entities.DriveFile): boolean => { ); &.n116_9 { - min-height: none; - max-height: none; + min-height: initial; + max-height: initial; aspect-ratio: 16 / 9; // fallback } &.n11_1{ - min-height: none; - max-height: none; + min-height: initial; + max-height: initial; aspect-ratio: 1 / 1; // fallback } &.n12_3 { - min-height: none; - max-height: none; + min-height: initial; + max-height: initial; aspect-ratio: 2 / 3; // fallback } } diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index bb5c6c7aab..bc6e3e4171 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -431,6 +431,7 @@ defineExpose({ margin: auto; padding: 32px; display: flex; + overflow: auto; @media (max-width: 500px) { padding: 16px; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index deeae6e940..02431a4557 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -341,6 +341,7 @@ function reply(viaKeyboard = false): void { pleaseLogin(); os.post({ reply: appearNote, + channel: appearNote.channel, animation: !viaKeyboard, }, () => { focus(); @@ -758,6 +759,7 @@ function showReactions(): void { padding: 16px; border: dashed 1px var(--renote); border-radius: 8px; + overflow: clip; } .channel { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 1f8a36b8de..a40b9cd2bd 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -320,6 +320,7 @@ function reply(viaKeyboard = false): void { showMovedDialog(); os.post({ reply: appearNote, + channel: appearNote.channel, animation: !viaKeyboard, }, () => { focus(); @@ -595,6 +596,7 @@ if (appearNote.replyId) { padding: 16px; border: dashed 1px var(--renote); border-radius: 8px; + overflow: clip; } .channel { diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 21be1454a7..98ea91d6be 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -37,7 +37,6 @@ const showContent = $ref(false); display: flex; margin: 0; padding: 0; - overflow: clip; font-size: 0.95em; } diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index f516ccbad8..0527811ab0 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -540,7 +540,7 @@ function onCompositionEnd(ev: CompositionEvent) { } async function onPaste(ev: ClipboardEvent) { - for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) { + for (const { item, i } of Array.from(ev.clipboardData.items, (item, i) => ({ item, i }))) { if (item.kind === 'file') { const file = item.getAsFile(); const lio = file.name.lastIndexOf('.'); @@ -907,7 +907,6 @@ defineExpose({ display: flex; flex-wrap: nowrap; gap: 4px; - margin-bottom: -10px; } .headerLeft { @@ -1025,7 +1024,7 @@ defineExpose({ } .targetNote { - padding: 10px 20px 16px 20px; + padding: 0 20px 16px 20px; } .withQuote { diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 6e4e054aad..21e76b766b 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -16,8 +16,8 @@ import tinycolor from 'tinycolor2'; const loaded = !!window.TagCanvas; const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz'; const computedStyle = getComputedStyle(document.documentElement); -const idForCanvas = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); -const idForTags = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); +const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); +const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); let available = $ref(false); let rootEl = $shallowRef<HTMLElement | null>(null); let canvasEl = $shallowRef<HTMLCanvasElement | null>(null); diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 2595ebc45d..062d0bd87a 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -38,14 +38,6 @@ const prepend = note => { } }; -const onUserAdded = () => { - tlComponent.pagingComponent?.reload(); -}; - -const onUserRemoved = () => { - tlComponent.pagingComponent?.reload(); -}; - let endpoint; let query; let connection; @@ -125,8 +117,6 @@ if (props.src === 'antenna') { listId: props.list, }); connection.on('note', prepend); - connection.on('userAdded', onUserAdded); - connection.on('userRemoved', onUserRemoved); } else if (props.src === 'channel') { endpoint = 'channels/timeline'; query = { diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 359bbeadc3..c6f089420e 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -110,7 +110,7 @@ const selectAll = () => { if (selectedEmojis.value.length > 0) { selectedEmojis.value = []; } else { - selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); + selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id); } }; diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 27a4cd0595..8b316c7182 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -166,7 +166,7 @@ const headerActions = $computed(() => []); const headerTabs = $computed(() => []); definePageMetadata(computed(() => page ? { - title: computed(() => page.title || page.name), + title: page.title || page.name, avatar: page.user, path: `/@${page.user.username}/pages/${page.name}`, share: { diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index cfe5cd31e7..31a288745e 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -258,6 +258,7 @@ watch([ showGapBetweenNotesInTimeline, instanceTicker, overridedDeviceKind, + mediaListWithOneImageAppearance, ], async () => { await reloadAsk(); }); diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 1aa1a5f81c..535a8820d2 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -32,7 +32,7 @@ </template> <script lang="ts" setup> -import { computed, onMounted, onUnmounted } from 'vue'; +import { computed, onMounted, onUnmounted, ref } from 'vue'; import { v4 as uuid } from 'uuid'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; @@ -93,15 +93,6 @@ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ 'darkTheme', 'syncDeviceDarkMode', 'plugins', - 'mediaVolume', - 'sound_masterVolume', - 'sound_note', - 'sound_noteMy', - 'sound_notification', - 'sound_chat', - 'sound_chatBg', - 'sound_antenna', - 'sound_channel', ]; const scope = ['clientPreferencesProfiles']; @@ -125,18 +116,18 @@ type Profile = { const connection = $i && useStream().useChannel('main'); -let profiles = $ref<Record<string, Profile> | null>(null); +const profiles = ref<Record<string, Profile> | null>(null); os.api('i/registry/get-all', { scope }) .then(res => { - profiles = res || {}; + profiles.value = res || {}; }); function isObject(value: unknown): value is Record<string, unknown> { return value != null && typeof value === 'object' && !Array.isArray(value); } -function validate(profile: unknown): void { +function validate(profile: any): void { if (!isObject(profile)) throw new Error('not an object'); // Check if unnecessary properties exist @@ -147,9 +138,9 @@ function validate(profile: unknown): void { // Check if createdAt and updatedAt is Date // https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date - if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt).getTime())) throw new Error('createdAt is falsy or not Date'); + if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw new Error('createdAt is falsy or not Date'); if (profile.updatedAt) { - if (Number.isNaN(new Date(profile.updatedAt).getTime())) { + if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) { throw new Error('updatedAt is not Date'); } } else if (profile.updatedAt !== null) { @@ -181,14 +172,14 @@ function getSettings(): Profile['settings'] { } async function saveNew(): Promise<void> { - if (!profiles) return; + if (!profiles.value) return; const { canceled, result: name } = await os.inputText({ title: ts._preferencesBackups.inputName, }); if (canceled) return; - if (Object.values(profiles).some(x => x.name === name)) { + if (Object.values(profiles.value).some(x => x.name === name)) { return os.alert({ title: ts._preferencesBackups.cannotSave, text: t('_preferencesBackups.nameAlreadyExists', { name }), @@ -212,7 +203,7 @@ function loadFile(): void { input.type = 'file'; input.multiple = false; input.onchange = async () => { - if (!profiles) return; + if (!profiles.value) return; if (!input.files || input.files.length === 0) return; const file = input.files[0]; @@ -233,7 +224,7 @@ function loadFile(): void { return os.alert({ type: 'error', title: ts._preferencesBackups.cannotLoad, - text: err?.message, + text: (err as any)?.message ?? '', }); } @@ -252,9 +243,9 @@ function loadFile(): void { } async function applyProfile(id: string): Promise<void> { - if (!profiles) return; + if (!profiles.value) return; - const profile = profiles[id]; + const profile = profiles.value[id]; const { canceled: cancel1 } = await os.confirm({ type: 'warning', @@ -312,23 +303,23 @@ async function applyProfile(id: string): Promise<void> { } async function deleteProfile(id: string): Promise<void> { - if (!profiles) return; + if (!profiles.value) return; const { canceled } = await os.confirm({ type: 'info', title: ts.delete, - text: t('deleteAreYouSure', { x: profiles[id].name }), + text: t('deleteAreYouSure', { x: profiles.value[id].name }), }); if (canceled) return; await os.apiWithDialog('i/registry/remove', { scope, key: id }); - delete profiles[id]; + delete profiles.value[id]; } async function save(id: string): Promise<void> { - if (!profiles) return; + if (!profiles.value) return; - const { name, createdAt } = profiles[id]; + const { name, createdAt } = profiles.value[id]; const { canceled } = await os.confirm({ type: 'info', @@ -349,25 +340,25 @@ async function save(id: string): Promise<void> { } async function rename(id: string): Promise<void> { - if (!profiles) return; + if (!profiles.value) return; const { canceled: cancel1, result: name } = await os.inputText({ title: ts._preferencesBackups.inputName, }); - if (cancel1 || profiles[id].name === name) return; + if (cancel1 || profiles.value[id].name === name) return; - if (Object.values(profiles).some(x => x.name === name)) { + if (Object.values(profiles.value).some(x => x.name === name)) { return os.alert({ title: ts._preferencesBackups.cannotSave, text: t('_preferencesBackups.nameAlreadyExists', { name }), }); } - const registry = Object.assign({}, { ...profiles[id] }); + const registry = Object.assign({}, { ...profiles.value[id] }); const { canceled: cancel2 } = await os.confirm({ type: 'info', - title: ts._preferencesBackups.rename, + title: ts.rename, text: t('_preferencesBackups.renameConfirm', { old: registry.name, new: name }), }); if (cancel2) return; @@ -377,7 +368,7 @@ async function rename(id: string): Promise<void> { } function menu(ev: MouseEvent, profileId: string) { - if (!profiles) return; + if (!profiles.value) return; return os.popupMenu([{ text: ts._preferencesBackups.apply, @@ -387,8 +378,8 @@ function menu(ev: MouseEvent, profileId: string) { type: 'a', text: ts.download, icon: 'ti ti-download', - href: URL.createObjectURL(new Blob([JSON.stringify(profiles[profileId], null, 2)], { type: 'application/json' })), - download: `${profiles[profileId].name}.json`, + href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })), + download: `${profiles.value[profileId].name}.json`, }, null, { text: ts.rename, icon: 'ti ti-forms', @@ -402,16 +393,16 @@ function menu(ev: MouseEvent, profileId: string) { icon: 'ti ti-trash', action: () => deleteProfile(profileId), danger: true, - }], ev.currentTarget ?? ev.target); + }], (ev.currentTarget ?? ev.target ?? undefined) as unknown as HTMLElement | undefined); } onMounted(() => { // streamingのuser storage updateイベントを監視して更新 connection?.on('registryUpdated', ({ scope: recievedScope, key, value }) => { if (!recievedScope || recievedScope.length !== scope.length || recievedScope[0] !== scope[0]) return; - if (!profiles) return; + if (!profiles.value) return; - profiles[key] = value; + profiles.value[key] = value; }); }); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 5453fe827d..c8b90b4fd7 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -11,7 +11,6 @@ export function createAiScriptEnv(opts) { USER_NAME: $i ? values.STR($i.name) : values.NULL, USER_USERNAME: $i ? values.STR($i.username) : values.NULL, CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), - CURRENT_URL: values.STR(window.location.href), 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { await os.alert({ type: type ? type.value : 'info', @@ -48,5 +47,8 @@ export function createAiScriptEnv(opts) { utils.assertString(key); return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`))); }), + 'Mk:url': values.FN_NATIVE(() => { + return values.STR(window.location.href); + }), }; } diff --git a/packages/frontend/src/scripts/emoji-base.ts b/packages/frontend/src/scripts/emoji-base.ts index 3f05642d57..e91f2fa22d 100644 --- a/packages/frontend/src/scripts/emoji-base.ts +++ b/packages/frontend/src/scripts/emoji-base.ts @@ -2,7 +2,7 @@ const twemojiSvgBase = '/twemoji'; const fluentEmojiPngBase = '/fluent-emoji'; export function char2twemojiFilePath(char: string): string { - let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); + let codes = Array.from(char, x => x.codePointAt(0)?.toString(16)); if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); codes = codes.filter(x => x && x.length); const fileName = codes.join('-'); @@ -10,7 +10,7 @@ export function char2twemojiFilePath(char: string): string { } export function char2fluentEmojiFilePath(char: string): string { - let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); + let codes = Array.from(char, x => x.codePointAt(0)?.toString(16)); // Fluent Emojiは国旗非対応 https://github.com/microsoft/fluentui-emoji/issues/25 if (codes[0]?.startsWith('1f1')) return char2twemojiFilePath(char); if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index 44a58d6c7d..891da6eb78 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -12,7 +12,8 @@ export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promi input.type = 'file'; input.multiple = multiple; input.onchange = () => { - const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal)); + if (!input.files) return res([]); + const promises = Array.from(input.files, file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal)); Promise.all(promises).then(driveFiles => { res(driveFiles); diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index bd74db7c85..85ad442ba4 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -6,11 +6,13 @@ --marginHalf: 10px; --margin: var(--marginFull); - --minBottomSpacing: 0px; + + // switch dynamically + --minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px))); + --minBottomSpacing: var(--minBottomSpacingMobile); @media (max-width: 500px) { --margin: var(--marginHalf); - --minBottomSpacing: calc(72px + max(12px, env(safe-area-inset-bottom, 0px))); } //--ad: rgb(255 169 0 / 10%); diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 9ae43c39d3..50866b23ed 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -207,9 +207,11 @@ watch($$(navFooter), () => { if (navFooter) { navFooterHeight = navFooter.offsetHeight; document.body.style.setProperty('--stickyBottom', `${navFooterHeight}px`); + document.body.style.setProperty('--minBottomSpacing', 'var(--minBottomSpacingMobile)'); } else { navFooterHeight = 0; document.body.style.setProperty('--stickyBottom', '0px'); + document.body.style.setProperty('--minBottomSpacing', '0px'); } }, { immediate: true, |