mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-25 07:25:04 +02:00
wip
This commit is contained in:
@@ -61,7 +61,6 @@ it('Composition API (standard)', () => {
|
||||
import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
|
||||
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
|
||||
import './photoswipe-!~{003}~.js';
|
||||
|
||||
const _hoisted_1 = /* @__PURE__ */ createBaseVNode("i", { class: "ti ti-photo" }, null, -1);
|
||||
const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
@@ -179,7 +178,6 @@ export { index_photos as default };
|
||||
import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
|
||||
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
|
||||
import './photoswipe-!~{003}~.js';
|
||||
|
||||
const _hoisted_1 = /* @__PURE__ */ createBaseVNode("i", { class: "ti ti-photo" }, null, -1);
|
||||
const index_photos = /* @__PURE__ */ defineComponent({
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.4",
|
||||
"punycode.js": "2.3.1",
|
||||
"qr-code-styling": "1.9.2",
|
||||
"qr-scanner": "1.4.2",
|
||||
|
||||
@@ -99,11 +99,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
// タッチデバイスでCSSの:hoverを機能させる
|
||||
window.document.addEventListener('touchend', () => {}, { passive: true });
|
||||
|
||||
// URLに#pswpを含む場合は取り除く
|
||||
if (window.location.hash === '#pswp') {
|
||||
window.history.replaceState(null, '', window.location.href.replace('#pswp', ''));
|
||||
}
|
||||
|
||||
// 一斉リロード
|
||||
reloadChannel.addEventListener('message', path => {
|
||||
if (path !== null) window.location.href = path;
|
||||
|
||||
209
packages/frontend/src/components/MkImageGallery.item.vue
Normal file
209
packages/frontend/src/components/MkImageGallery.item.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div ref="rootEl" :class="$style.root" :style="{ transform: `translate3d(${translation.x}px, ${translation.y}px, 0)` }">
|
||||
<img
|
||||
ref="imageEl"
|
||||
:class="$style.image"
|
||||
:src="image.src"
|
||||
:width="size.width"
|
||||
:height="size.height"
|
||||
@wheel="onWheel"
|
||||
@touchstart="onTouchstart"
|
||||
@pointerdown="onPointerdown"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
type Image = {
|
||||
id: string;
|
||||
src: string;
|
||||
width: number;
|
||||
height: number;
|
||||
sourceElement?: HTMLElement;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
image: Image;
|
||||
}>(), {
|
||||
});
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const imageEl = useTemplateRef('imageEl');
|
||||
|
||||
const padding = 30;
|
||||
|
||||
function calcDefaultSize(image: Image) {
|
||||
const maxWidth = window.innerWidth - padding * 2;
|
||||
const maxHeight = window.innerHeight - padding * 2;
|
||||
|
||||
let width = image.width;
|
||||
let height = image.height;
|
||||
|
||||
if (width > maxWidth) {
|
||||
height = height * (maxWidth / width);
|
||||
width = maxWidth;
|
||||
}
|
||||
|
||||
if (height > maxHeight) {
|
||||
width = width * (maxHeight / height);
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
function calcDefaultTranslation(image: Image) {
|
||||
const defaultSize = calcDefaultSize(image);
|
||||
|
||||
const x = (window.innerWidth - defaultSize.width) / 2;
|
||||
const y = (window.innerHeight - defaultSize.height) / 2;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
const defaultSize = calcDefaultSize(props.image);
|
||||
const defaultTranslation = calcDefaultTranslation(props.image);
|
||||
|
||||
const size = ref({ width: defaultSize.width, height: defaultSize.height });
|
||||
const translation = ref({ x: defaultTranslation.x, y: defaultTranslation.y });
|
||||
|
||||
const isZooming = ref(false);
|
||||
|
||||
function zoomInTo(x: number, y: number, factor = 1.1) {
|
||||
const newWidth = size.value.width * factor;
|
||||
const newHeight = size.value.height * factor;
|
||||
|
||||
size.value.width = newWidth;
|
||||
size.value.height = newHeight;
|
||||
|
||||
// Center the image on the cursor
|
||||
const rect = imageEl.value?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
const offsetX = x - rect.left;
|
||||
const offsetY = y - rect.top;
|
||||
|
||||
translation.value.x -= offsetX * (factor - 1);
|
||||
translation.value.y -= offsetY * (factor - 1);
|
||||
isZooming.value = true;
|
||||
}
|
||||
|
||||
function onWheel(event: WheelEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
const delta = event.deltaY;
|
||||
|
||||
const scaleFactor = 1.1;
|
||||
const scale = delta > 0 ? 1 / scaleFactor : scaleFactor;
|
||||
|
||||
const newWidth = size.value.width * scale;
|
||||
const newHeight = size.value.height * scale;
|
||||
|
||||
if (newWidth < defaultSize.width || newHeight < defaultSize.height) {
|
||||
size.value.width = defaultSize.width;
|
||||
size.value.height = defaultSize.height;
|
||||
translation.value.x = defaultTranslation.x;
|
||||
translation.value.y = defaultTranslation.y;
|
||||
isZooming.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
zoomInTo(event.clientX, event.clientY, scale);
|
||||
}
|
||||
|
||||
let lastTapTime = 0;
|
||||
|
||||
function onTouchstart(event: TouchEvent) {
|
||||
if (isZooming.value) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.touches.length !== 1) return;
|
||||
|
||||
const touch = event.touches[0];
|
||||
|
||||
let tapTimeout: number | null = null;
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
const tapLength = currentTime - lastTapTime;
|
||||
|
||||
if (tapLength < 300 && tapLength > 0) { // ダブルタップ
|
||||
event.preventDefault();
|
||||
|
||||
if (isZooming.value) {
|
||||
size.value.width = defaultSize.width;
|
||||
size.value.height = defaultSize.height;
|
||||
translation.value.x = defaultTranslation.x;
|
||||
translation.value.y = defaultTranslation.y;
|
||||
isZooming.value = false;
|
||||
} else {
|
||||
zoomInTo(touch.clientX, touch.clientY, 2);
|
||||
}
|
||||
}
|
||||
|
||||
lastTapTime = currentTime;
|
||||
|
||||
if (tapTimeout) clearTimeout(tapTimeout);
|
||||
|
||||
tapTimeout = window.setTimeout(() => {
|
||||
tapTimeout = null;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// ズーム中、ドラッグされたら画像を移動する
|
||||
let isDragging = false;
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
|
||||
function onPointerdown(event: PointerEvent) {
|
||||
if (!isZooming.value) return;
|
||||
|
||||
isDragging = true;
|
||||
lastX = event.clientX;
|
||||
lastY = event.clientY;
|
||||
|
||||
const onPointerMove = (moveEvent: PointerEvent) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const deltaX = moveEvent.clientX - lastX;
|
||||
const deltaY = moveEvent.clientY - lastY;
|
||||
|
||||
translation.value.x += deltaX;
|
||||
translation.value.y += deltaY;
|
||||
|
||||
lastX = moveEvent.clientX;
|
||||
lastY = moveEvent.clientY;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
isDragging = false;
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
window.removeEventListener('pointerup', onPointerUp);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onPointerMove);
|
||||
window.addEventListener('pointerup', onPointerUp);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
//transition: transform 0.2s ease;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
//transition: width 0.2s ease, height 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
84
packages/frontend/src/components/MkImageGallery.vue
Normal file
84
packages/frontend/src/components/MkImageGallery.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div ref="rootEl" :class="$style.root" :style="{ zIndex }">
|
||||
<div :class="[$style.bg]"></div>
|
||||
<div ref="mainEl" :class="$style.main">
|
||||
<div :class="$style.items">
|
||||
<div v-for="image in images" :key="image.src" :class="$style.item">
|
||||
<XItem :image="image"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||
import XItem from './MkImageGallery.item.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
type Image = {
|
||||
id: string;
|
||||
src: string;
|
||||
width: number;
|
||||
height: number;
|
||||
sourceElement?: HTMLElement;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
defaultId?: string;
|
||||
images: Image[];
|
||||
}>(), {
|
||||
});
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const mainEl = useTemplateRef('mainEl');
|
||||
const zIndex = os.claimZIndex('high');
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #0f08;
|
||||
}
|
||||
|
||||
.main {
|
||||
position: absolute;
|
||||
|
||||
}
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
overflow-x: auto;
|
||||
overflow-y: clip;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
overflow: clip;
|
||||
contain: strict;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<template v-for="media in mediaList.filter(media => previewable(media))">
|
||||
<XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/>
|
||||
<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/>
|
||||
<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :disableImageLink="true" :class="$style.media" :data-id="media.id" :image="media" :raw="raw" @click="openGallery(media.id)"/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,9 +31,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||
import PhotoSwipe from 'photoswipe';
|
||||
import 'photoswipe/style.css';
|
||||
import { FILE_TYPE_BROWSERSAFE } from '@@/js/const.js';
|
||||
import XBanner from '@/components/MkMediaBanner.vue';
|
||||
import XImage from '@/components/MkMediaImage.vue';
|
||||
@@ -48,18 +45,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const gallery = useTemplateRef('gallery');
|
||||
const pswpZIndex = os.claimZIndex('middle');
|
||||
window.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 = null;
|
||||
|
||||
let activeEl: HTMLElement | null = null;
|
||||
|
||||
const popstateHandler = (): void => {
|
||||
if (lightbox?.pswp && lightbox.pswp.isOpen === true) {
|
||||
lightbox.pswp.close();
|
||||
}
|
||||
};
|
||||
|
||||
async function calcAspectRatio() {
|
||||
if (!gallery.value) return;
|
||||
@@ -96,121 +82,9 @@ onMounted(() => {
|
||||
calcAspectRatio();
|
||||
|
||||
if (gallery.value == null) return; // TSを黙らすため
|
||||
|
||||
lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: props.mediaList
|
||||
.filter(media => {
|
||||
if (media.type === 'image/svg+xml') return true; // svgのwebpublicはpngなのでtrue
|
||||
return media.type.startsWith('image') && FILE_TYPE_BROWSERSAFE.includes(media.type);
|
||||
})
|
||||
.map(media => {
|
||||
const item = {
|
||||
src: media.url,
|
||||
w: media.properties.width,
|
||||
h: media.properties.height,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
alt: media.comment || media.name,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
comment: media.comment || media.name,
|
||||
};
|
||||
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
||||
[item.w, item.h] = [item.h, item.w];
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
gallery: gallery.value,
|
||||
mainClass: 'pswp',
|
||||
children: '.image',
|
||||
thumbSelector: '.image',
|
||||
loop: false,
|
||||
padding: window.innerWidth > 500 ? {
|
||||
top: 32,
|
||||
bottom: 90,
|
||||
left: 32,
|
||||
right: 32,
|
||||
} : {
|
||||
top: 0,
|
||||
bottom: 78,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
imageClickAction: 'close',
|
||||
tapAction: 'close',
|
||||
bgOpacity: 1,
|
||||
showAnimationDuration: 100,
|
||||
hideAnimationDuration: 100,
|
||||
returnFocus: false,
|
||||
pswpModule: PhotoSwipe,
|
||||
});
|
||||
|
||||
lightbox.addFilter('itemData', (itemData) => {
|
||||
// element is children
|
||||
const { element } = itemData;
|
||||
|
||||
const id = element?.dataset.id;
|
||||
const file = props.mediaList.find(media => media.id === id);
|
||||
if (!file) return itemData;
|
||||
|
||||
itemData.src = file.url;
|
||||
itemData.w = Number(file.properties.width);
|
||||
itemData.h = Number(file.properties.height);
|
||||
if (file.properties.orientation != null && file.properties.orientation >= 5) {
|
||||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||
}
|
||||
itemData.msrc = file.thumbnailUrl ?? undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
itemData.alt = file.comment || file.name;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
itemData.comment = file.comment || file.name;
|
||||
itemData.thumbCropped = true;
|
||||
|
||||
return itemData;
|
||||
});
|
||||
|
||||
lightbox.on('uiRegister', () => {
|
||||
lightbox?.pswp?.ui?.registerElement({
|
||||
name: 'altText',
|
||||
className: 'pswp__alt-text-container',
|
||||
appendTo: 'wrapper',
|
||||
onInit: (el, pswp) => {
|
||||
const textBox = window.document.createElement('p');
|
||||
textBox.className = 'pswp__alt-text _acrylic';
|
||||
el.appendChild(textBox);
|
||||
|
||||
pswp.on('change', () => {
|
||||
textBox.textContent = pswp.currSlide?.data.comment;
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
lightbox.on('afterInit', () => {
|
||||
activeEl = window.document.activeElement instanceof HTMLElement ? window.document.activeElement : null;
|
||||
focusParent(activeEl, true, true);
|
||||
lightbox?.pswp?.element?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
window.history.pushState(null, '', '#pswp');
|
||||
});
|
||||
|
||||
lightbox.on('destroy', () => {
|
||||
focusParent(activeEl, true, false);
|
||||
activeEl = null;
|
||||
if (window.location.hash === '#pswp') {
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', popstateHandler);
|
||||
|
||||
lightbox.init();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('popstate', popstateHandler);
|
||||
lightbox?.destroy();
|
||||
lightbox = null;
|
||||
activeEl = null;
|
||||
});
|
||||
|
||||
const previewable = (file: Misskey.entities.DriveFile): boolean => {
|
||||
@@ -219,11 +93,20 @@ const previewable = (file: Misskey.entities.DriveFile): boolean => {
|
||||
return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type);
|
||||
};
|
||||
|
||||
const openGallery = () => {
|
||||
if (props.mediaList.filter(media => previewable(media)).length > 0) {
|
||||
lightbox?.loadAndOpen(0);
|
||||
}
|
||||
};
|
||||
async function openGallery(id: string) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageGallery.vue').then(x => x.default), {
|
||||
defaultId: id,
|
||||
images: props.mediaList.filter(media => previewable(media)).map(media => ({
|
||||
id: media.id,
|
||||
src: media.url,
|
||||
width: media.properties.width ?? 0,
|
||||
height: media.properties.height ?? 0,
|
||||
sourceElement: gallery.value?.querySelector(`.image[data-id="${media.id}"]`) as HTMLElement | undefined,
|
||||
})),
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openGallery,
|
||||
|
||||
@@ -53,7 +53,6 @@ let latestHotkey: Pattern & { callback: CallbackFunction } | null = null;
|
||||
export const makeHotkey = (keymap: Keymap, ignoreElements = IGNORE_ELEMENTS) => {
|
||||
const actions = parseKeymap(keymap);
|
||||
return (ev: KeyboardEvent) => {
|
||||
if ('pswp' in window && window.pswp != null) return;
|
||||
if (window.document.activeElement != null) {
|
||||
if (ignoreElements.includes(window.document.activeElement.tagName.toLowerCase())) return;
|
||||
if (getHTMLElementOrNull(window.document.activeElement)?.isContentEditable) return;
|
||||
|
||||
@@ -220,9 +220,6 @@ export function getConfig(): UserConfig {
|
||||
groups: [{
|
||||
name: 'vue',
|
||||
test: /node_modules[\\/]vue/,
|
||||
}, {
|
||||
name: 'photoswipe',
|
||||
test: /node_modules[\\/]photoswipe/,
|
||||
}, {
|
||||
// split i18n related module to distinct module
|
||||
name: 'i18n',
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -646,9 +646,6 @@ importers:
|
||||
misskey-reversi:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-reversi
|
||||
photoswipe:
|
||||
specifier: 5.4.4
|
||||
version: 5.4.4
|
||||
punycode.js:
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1
|
||||
@@ -7237,10 +7234,6 @@ packages:
|
||||
pgpass@1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||
|
||||
photoswipe@5.4.4:
|
||||
resolution: {integrity: sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==}
|
||||
engines: {node: '>= 0.12.0'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -15637,8 +15630,6 @@ snapshots:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
||||
photoswipe@5.4.4: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.2: {}
|
||||
|
||||
Reference in New Issue
Block a user