Merge remote-tracking branch 'prismisskey/develop' into prismisskey

This commit is contained in:
mattyatea 2023-09-20 09:05:40 +09:00
commit b853625500
34 changed files with 38058 additions and 342 deletions

View file

@ -130,6 +130,7 @@
"storybook": "7.4.1",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-compression2": "^0.10.4",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.4",
"vitest-fetch-mock": "0.2.2",

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root" :style="bg">
<img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/>
<img v-if="faviconUrl && !defaultStore.state.enableUltimateDataSaverMode" :class="$style.icon" :src="faviconUrl"/>
<div :class="$style.name">{{ instance.name }}</div>
</div>
</template>
@ -15,6 +15,7 @@ import { } from 'vue';
import { instanceName } from '@/config';
import { instance as Instance } from '@/instance';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
import {defaultStore} from "@/store";
const props = defineProps<{
instance?: {

View file

@ -88,6 +88,15 @@ if (props.src === 'antenna') {
withReplies: defaultStore.state.showTimelineReplies,
});
connection.on('note', prepend);
} else if (props.src === 'all') {
endpoint = 'notes/hybrid-all-timeline';
query = {
withReplies: defaultStore.state.showTimelineReplies,
};
connection = stream.useChannel('hybridAllTimeline', {
withReplies: defaultStore.state.showTimelineReplies,
});
connection.on('note', prepend);
} else if (props.src === 'global') {
endpoint = 'notes/global-timeline';
query = {

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
<MkImgWithBlurhash :class="$style.inner" :src="defaultStore.state.enableUltimateDataSaverMode ? undefined : url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
<div v-if="user.isCat" :class="[$style.ears]">
<div :class="$style.earLeft">

View file

@ -38,13 +38,20 @@ const rawUrl = computed(() => {
const url = computed(() => {
if (rawUrl.value == null) return null;
const useOriginalSize = props.useOriginalSize;
const enableDataSaverMode = defaultStore.state.enableUltimateDataSaverMode;
let datasaver_result ;
if (enableDataSaverMode) {
datasaver_result = useOriginalSize ? undefined : 'datasaver';
} else {
datasaver_result = useOriginalSize ? undefined : 'emoji';
}
const proxied =
(rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value))
? rawUrl.value
: getProxiedImageUrl(
rawUrl.value,
props.useOriginalSize ? undefined : 'emoji',
datasaver_result,
false,
true,
);

View file

@ -0,0 +1,46 @@
<template>
<span v-if="errored">{{ alt }}</span>
<img v-else :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps<{
name: string;
normal?: boolean;
url: string;
}>();
const rawUrl = computed(() => props.url);
const url = computed(() => rawUrl.value);
const alt = computed(() => props.name);
let errored = $ref(url.value == null);
</script>
<style lang="scss" module>
.root {
height: 2em;
vertical-align: middle;
transition: transform 0.2s ease;
&:hover {
transform: scale(1.2);
}
}
.normal {
height: 1.25em;
vertical-align: -0.25em;
&:hover {
transform: none;
}
}
.noStyle {
height: auto !important;
}
</style>

View file

@ -1,8 +1,3 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { VNode, h } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
@ -11,12 +6,14 @@ import MkLink from '@/components/MkLink.vue';
import MkMention from '@/components/MkMention.vue';
import MkEmoji from '@/components/global/MkEmoji.vue';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
import MkEmojiKitchen from '@/components/global/MkEmojiKitchen.vue';
import MkCode from '@/components/MkCode.vue';
import MkGoogle from '@/components/MkGoogle.vue';
import MkSparkle from '@/components/MkSparkle.vue';
import MkA from '@/components/global/MkA.vue';
import { host } from '@/config';
import { defaultStore } from '@/store';
import { mixEmoji } from '@/scripts/emojiKitchen/emojiMixer';
const QUOTE_STYLE = `
display: block;
@ -27,6 +24,38 @@ border-left: solid 3px var(--fg);
opacity: 0.7;
`.split('\n').join(' ');
const colorRegexp = /^([0-9a-f]{3,4}?|[0-9a-f]{6}?|[0-9a-f]{8}?)$/i;
function checkColorHex(text: string) {
return colorRegexp.test(text);
}
const gradientCounterRegExp = /^(color|step)(\d+)/;
function toGradientText(args: Record<string, string>) {
const colors: { index: number; step?: string, color?: string }[] = [];
for (const k in args) {
const matches = k.match(gradientCounterRegExp);
if (matches == null) continue;
const mindex = parseInt(matches[2]);
let i = colors.findIndex(v => v.index === mindex);
if (i === -1) {
i = colors.length;
colors.push({ index: mindex });
}
colors[i][matches[1]] = args[k];
}
let deg = parseFloat(args.deg || '90');
let res = `linear-gradient(${deg}deg`;
for (const colorProp of colors.sort((a, b) => a.index - b.index)) {
let color = colorProp.color;
if (!color || !checkColorHex(color)) color = 'f00';
let step = parseFloat(colorProp.step ?? '');
let stepText = isNaN(step) ? '' : ` ${step}%`;
res += `, #${color}${stepText}`;
}
return res + ')';
}
export default function(props: {
text: string;
plain?: boolean;
@ -44,7 +73,7 @@ export default function(props: {
const ast = (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
const validTime = (t: string | null | undefined) => {
if (t == null) return null;
if (t == null || typeof t === 'boolean') return null;
return t.match(/^[0-9.]+s$/) ? t : null;
};
@ -170,18 +199,15 @@ export default function(props: {
break;
}
case 'blur': {
const radius = parseFloat(token.props.args.rad ?? '6');
return h('span', {
class: '_mfm_blur_',
style: `--blur-px: ${radius}px;`
}, genEl(token.children, scale));
}
case 'rainbow': {
if (!useAnim) {
return h('span', {
class: '_mfm_rainbow_fallback_',
}, genEl(token.children, scale));
}
const speed = validTime(token.props.args.speed) ?? '1s';
style = `animation: mfm-rainbow ${speed} linear infinite;`;
style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
break;
}
case 'sparkle': {
@ -192,7 +218,23 @@ export default function(props: {
}
case 'rotate': {
const degrees = parseFloat(token.props.args.deg ?? '90');
style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
let rotateText = `rotate(${degrees}deg)`;
if (!token.props.args.deg && (token.props.args.x || token.props.args.y || token.props.args.z)) {
rotateText = '';
}
if (token.props.args.x) {
const degrees = parseFloat(token.props.args.x ?? '0');
rotateText += ` rotateX(${degrees}deg)`;
}
if (token.props.args.y) {
const degrees = parseFloat(token.props.args.y ?? '0');
rotateText += ` rotateY(${degrees}deg)`;
}
if (token.props.args.z) {
const degrees = parseFloat(token.props.args.z ?? '0');
rotateText += ` rotateZ(${degrees}deg)`;
}
style = `transform: ${rotateText}; transform-origin: center center;`;
break;
}
case 'position': {
@ -213,18 +255,101 @@ export default function(props: {
scale = scale * Math.max(x, y);
break;
}
case 'skew': {
if (!defaultStore.state.advancedMfm) {
style = '';
break;
}
const x = parseFloat(token.props.args.x ?? '0');
const y = parseFloat(token.props.args.y ?? '0');
style = `transform: skew(${x}deg, ${y}deg);`;
break;
}
case 'fgg': {
if (!defaultStore.state.advancedMfm) break;
style = `-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: ${toGradientText(token.props.args)};`
break;
}
case 'fg': {
let color = token.props.args.color;
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
if (!checkColorHex(color)) color = 'f00';
style = `color: #${color};`;
break;
}
case 'bgg': {
if (!defaultStore.state.advancedMfm) break;
style = `background-image: ${toGradientText(token.props.args)};`
break;
}
case 'bg': {
let color = token.props.args.color;
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
if (!checkColorHex(color)) color = 'f00';
style = `background-color: #${color};`;
break;
}
case 'clip': {
if (!defaultStore.state.advancedMfm) break;
let path = '';
if (token.props.args.circle) {
const percent = parseFloat(token.props.args.circle ?? '');
const percentText = isNaN(percent) ? '' : `${percent}%`;
path = `circle(${percentText})`;
}
else {
const top = parseFloat(token.props.args.t ?? '0');
const bottom = parseFloat(token.props.args.b ?? '0');
const left = parseFloat(token.props.args.l ?? '0');
const right = parseFloat(token.props.args.r ?? '0');
path = `inset(${top}% ${right}% ${bottom}% ${left}%)`;
}
style = `clip-path: ${path};`;
break;
}
case 'move': {
const speed = validTime(token.props.args.speed) ?? '1s';
const fromX = parseFloat(token.props.args.fromx ?? '0');
const fromY = parseFloat(token.props.args.fromy ?? '0');
const toX = parseFloat(token.props.args.tox ?? '0');
const toY = parseFloat(token.props.args.toy ?? '0');
const ease =
token.props.args.ease ? 'ease' :
token.props.args.easein ? 'ease-in' :
token.props.args.easeout ? 'ease-out' :
token.props.args.easeinout ? 'ease-in-out' :
'linear';
const delay = validTime(token.props.args.delay) ?? '0s';
const direction =
token.props.args.rev && token.props.args.once ? 'reverse' :
token.props.args.rev ? 'alternate-reverse' :
token.props.args.once ? 'normal' :
'alternate';
style = useAnim ? `--move-fromX: ${fromX}em; --move-fromY: ${fromY}em; --move-toX: ${toX}em; --move-toY: ${toY}em; animation: ${speed} ${ease} ${delay} infinite ${direction} mfm-move;` : '';
break;
}
case 'mix': {
const ch = token.children;
if (ch.length != 2 || ch.some(c => c.type !== 'unicodeEmoji')) {
style = null;
break;
}
const emoji1 = ch[0].props.emoji;
const emoji2 = ch[1].props.emoji;
const mixedEmojiUrl = mixEmoji(emoji1, emoji2);
if (!mixedEmojiUrl) {
style = null;
break;
}
return h(MkEmojiKitchen, {
key: Math.random(),
name: emoji1 + emoji2,
normal: props.plain,
url: mixedEmojiUrl
});
}
}
if (style == null) {
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);

View file

@ -34,6 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._aboutMisskey.source }}
<template #suffix>GitHub</template>
</FormLink>
<FormLink to="https://github.com/mattyatea/misskey" external>
<template #icon><i class="ti ti-code"></i></template>
{{ i18n.ts._aboutMisskey.forksource }}
<template #suffix>GitHub</template>
</FormLink>
<FormLink to="https://crowdin.com/project/misskey" external>
<template #icon><i class="ti ti-language-hiragana"></i></template>
{{ i18n.ts._aboutMisskey.translation }}

View file

@ -113,7 +113,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
<MkSwitch v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
<MkSwitch :disabled="enableUltimateDataSaverMode || enableCellularWithUltimateDataSaver" v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
<MkSwitch :disabled="enableUltimateDataSaverMode || enableCellularWithUltimateDataSaver" v-model="enableCellularWithDataSaver">{{ i18n.ts.cellularWithDataSaver }}</MkSwitch>
<MkSwitch v-model="enableUltimateDataSaverMode">{{ i18n.ts.UltimateDataSaver }}</MkSwitch>
<MkSwitch v-model="enableCellularWithUltimateDataSaver">{{ i18n.ts.cellularWithUltimateDataSaver }}</MkSwitch>
</div>
<div>
<MkRadios v-model="emojiStyle">
@ -227,7 +230,10 @@ const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode')) ;
const enableCellularWithDataSaver = computed(defaultStore.makeGetterSetter('enableCellularWithDataSaver'));
const enableUltimateDataSaverMode = computed(defaultStore.makeGetterSetter('enableUltimateDataSaverMode'))
const enableCellularWithUltimateDataSaver = computed(defaultStore.makeGetterSetter('enableCellularWithUltimateDataSaver'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@ -242,6 +248,8 @@ const notificationPosition = computed(defaultStore.makeGetterSetter('notificatio
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
const showTimelineReplies = computed(defaultStore.makeGetterSetter('showTimelineReplies'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
miLocalStorage.removeItem('locale');

View file

@ -47,6 +47,7 @@ const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
const isAdmin = ($i != null && $i.isAdmin);
const keymap = {
't': focus,
};
@ -102,7 +103,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
os.popupMenu(items, ev.currentTarget ?? ev.target);
}
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global'): void {
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | 'all'): void {
defaultStore.set('tl', {
...defaultStore.state.tl,
src: newSrc,
@ -145,6 +146,11 @@ const headerTabs = $computed(() => [{
title: i18n.ts._timelines.global,
icon: 'ti ti-whirl',
iconOnly: true,
}] : []), ...(isAdmin ? [{
key: 'all',
title: 'all',
icon: 'ti ti-whirl',
iconOnly: true,
}] : []), {
icon: 'ti ti-list',
title: i18n.ts.lists,

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
import * as data from './emojiData';
const mixEmojiUrl = (r, c) => {
let padZeros = r < 20220500; // Revisions before 0522 had preceding zeros
c[0] = c[0].split(/-/g).map(s => padZeros ? s.padStart(4, "0") : s).join("-u");
c[1] = c[1].split(/-/g).map(s => padZeros ? s.padStart(4, "0") : s).join("-u");
return `https://www.gstatic.com/android/keyboard/emojikitchen/${r}/u${c[0]}/u${c[0]}_u${c[1]}.png`;
};
const convertBase = (value, from_base, to_base) => {
value = value.toString();
var range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split('');
var from_range = range.slice(0, from_base);
var to_range = range.slice(0, to_base);
var dec_value = value.split('').reverse().reduce(function (carry, digit, index) {
if (from_range.indexOf(digit) === -1) throw new Error('Invalid digit `' + digit + '` for base ' + from_base + '.');
return carry += from_range.indexOf(digit) * (Math.pow(from_base, index));
}, 0);
var new_value = '';
while (dec_value > 0) {
new_value = to_range[dec_value % to_base] + new_value;
dec_value = (dec_value - (dec_value % to_base)) / to_base;
}
return new_value || '0';
};
const emojiSplit = String.fromCodePoint(0x200d);
const hexEncodeEmoji = (chr) => {
if (chr.length === 3) return hexEncodeEmoji(chr.slice(0, 2)) + '-' + hexEncodeEmoji(chr.slice(2, chr.length));
else if (chr.length === 2) {
const hi = chr.charCodeAt(0);
const lo = chr.charCodeAt(1);
if (0xD800 <= hi && hi < 0xDC00 && 0xDC00 <= lo && lo < 0xE000) {
return (0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00)).toString(16);
}
return hi.toString(16) + '-' + lo.toString(16);
}
else if (chr.length === 1) {
return chr.charCodeAt(0).toString(16);
}
else {
const sp = chr.split(emojiSplit);
if (sp.length !== 2) return '';
return hexEncodeEmoji(sp[0]) + '-200d-' + hexEncodeEmoji(sp[1]);
}
};
const pairsMatchingMap = match => {
const mv = match[0];
let [d, c1, c2] = mv.split('.');
c1 = data.points[convertBase(c1, 64, 10)];
c2 = data.points[convertBase(c2, 64, 10)];
d = data.revisions[convertBase(d, 64, 10)];
return mixEmojiUrl(d, [c1, c2]);
};
export const mixEmoji = (emoji1, emoji2) => {
const encordedEmoji1 = convertBase(data.points.indexOf(hexEncodeEmoji(emoji1)), 10, 64);
const encordedEmoji2 = convertBase(data.points.indexOf(hexEncodeEmoji(emoji2)), 10, 64);
return [
...data.pairs.matchAll(new RegExp("^.*\\." + encordedEmoji1 + "\\." + encordedEmoji2 + "\\.$", "gm")),
...data.pairs.matchAll(new RegExp("^.*\\." + encordedEmoji2 + "\\." + encordedEmoji1 + "\\.$", "gm"))
].map(pairsMatchingMap).pop();
};

View file

@ -1,6 +1 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'rotate'];
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'skew', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'fgg', 'bgg', 'clip', 'move', 'mix'];

View file

@ -207,6 +207,18 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
enableUltimateDataSaverMode: {
where: 'device',
default: false,
},
enableCellularWithDataSaver: {
where: 'device',
default: false,
},
enableCellularWithUltimateDataSaver: {
where: 'device',
default: false,
},
disableShowingAnimatedImages: {
where: 'device',
default: window.matchMedia('(prefers-reduced-motion)').matches,

View file

@ -4,108 +4,122 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
<div :class="$style.root">
<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
<MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu">
<template #header>
<div>
<XAnnouncements v-if="$i" :class="$style.announcements"/>
<XStatusBars :class="$style.statusbars"/>
</div>
</template>
<RouterView/>
<div :class="$style.spacer"></div>
</MkStickyContainer>
<MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;"
@contextmenu.stop="onContextmenu">
<template #header>
<div>
<XAnnouncements v-if="$i" :class="$style.announcements"/>
<XStatusBars :class="$style.statusbars"/>
</div>
</template>
<RouterView/>
<div :class="$style.spacer"></div>
</MkStickyContainer>
<div v-if="isDesktop" :class="$style.widgets">
<XWidgets/>
</div>
<div v-if="isDesktop" :class="$style.widgets">
<XWidgets/>
</div>
<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true">
<i class="ti ti-apps"></i></button>
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
</div>
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i
:class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated"
:class="$style.navButtonIndicator"><i
class="_indicatorCircle"></i></span></button>
<button :class="$style.navButton" class="_button"
@click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i
:class="$style.navButtonIcon" class="ti ti-home"></i></button>
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i
:class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification"
:class="$style.navButtonIndicator"><i
class="_indicatorCircle"></i></span></button>
<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon"
class="ti ti-apps"></i>
</button>
<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon"
class="ti ti-pencil"></i></button>
</div>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuDrawerBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuDrawerBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i
class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<XCommon/>
</div>
<XCommon/>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, shallowRef, Ref } from 'vue';
import {defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, shallowRef, Ref} from 'vue';
import XCommon from './_common_/common.vue';
import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
import { instanceName } from '@/config';
import {instanceName} from '@/config';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { navbarItemDef } from '@/navbar';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { mainRouter } from '@/router';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
import { deviceKind } from '@/scripts/device-kind';
import { miLocalStorage } from '@/local-storage';
import { CURRENT_STICKY_BOTTOM } from '@/const';
import { useScrollPositionManager } from '@/nirax';
import {defaultStore} from '@/store';
import {navbarItemDef} from '@/navbar';
import {i18n} from '@/i18n';
import {$i} from '@/account';
import {mainRouter} from '@/router';
import {PageMetadata, provideMetadataReceiver} from '@/scripts/page-metadata';
import {deviceKind} from '@/scripts/device-kind';
import {miLocalStorage} from '@/local-storage';
import {CURRENT_STICKY_BOTTOM} from '@/const';
import {useScrollPositionManager} from '@/nirax';
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
@ -115,11 +129,38 @@ const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announce
const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500;
onMounted(() => {
if (
window.navigator.connection.type === "cellular" &&
!defaultStore.state.enableUltimateDataSaverMode &&
defaultStore.state.enableCellularWithUltimateDataSaver
) {
defaultStore.state.enableDataSaverMode = true;
defaultStore.state.enableUltimateDataSaverMode = true;
} else if (window.navigator.connection.type !== "cellular" && window.navigator.connection.type !== "undefined" && defaultStore.state.enableDataSaverMode && defaultStore.state.enableCellularWithDataSaver) {
defaultStore.state.enableDataSaverMode = false;
defaultStore.state.enableUltimateDataSaverMode = true;
}
if (
window.navigator.connection.type === "cellular" &&
!defaultStore.state.enableDataSaverMode &&
defaultStore.state.enableCellularWithDataSaver
) {
defaultStore.state.enableDataSaverMode = true;
} else if (window.navigator.connection.type !== "cellular" && window.navigator.connection.type !== "undefined" && defaultStore.state.enableDataSaverMode && defaultStore.state.enableCellularWithDataSaver) {
defaultStore.state.enableDataSaverMode = false;
}
if (defaultStore.state.enableUltimateDataSaverMode) {
defaultStore.state.enableDataSaverMode = true;
}
});
// UI deviceKind === 'desktop'
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
@ -129,103 +170,103 @@ const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
provide('router', mainRouter);
provideMetadataReceiver((info) => {
pageMetadata = info;
if (pageMetadata.value) {
document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
pageMetadata = info;
if (pageMetadata.value) {
document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
});
const menuIndicated = computed(() => {
for (const def in navbarItemDef) {
if (def === 'notifications') continue; //
if (navbarItemDef[def].indicated) return true;
}
return false;
for (const def in navbarItemDef) {
if (def === 'notifications') continue; //
if (navbarItemDef[def].indicated) return true;
}
return false;
});
const drawerMenuShowing = ref(false);
mainRouter.on('change', () => {
drawerMenuShowing.value = false;
drawerMenuShowing.value = false;
});
if (window.innerWidth > 1024) {
const tempUI = miLocalStorage.getItem('ui_temp');
if (tempUI) {
miLocalStorage.setItem('ui', tempUI);
miLocalStorage.removeItem('ui_temp');
location.reload();
}
const tempUI = miLocalStorage.getItem('ui_temp');
if (tempUI) {
miLocalStorage.setItem('ui', tempUI);
miLocalStorage.removeItem('ui_temp');
location.reload();
}
}
defaultStore.loaded.then(() => {
if (defaultStore.state.widgets.length === 0) {
defaultStore.set('widgets', [{
name: 'calendar',
id: 'a', place: 'right', data: {},
}, {
name: 'notifications',
id: 'b', place: 'right', data: {},
}, {
name: 'trends',
id: 'c', place: 'right', data: {},
}]);
}
if (defaultStore.state.widgets.length === 0) {
defaultStore.set('widgets', [{
name: 'calendar',
id: 'a', place: 'right', data: {},
}, {
name: 'notifications',
id: 'b', place: 'right', data: {},
}, {
name: 'trends',
id: 'c', place: 'right', data: {},
}]);
}
});
onMounted(() => {
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, { passive: true });
}
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, {passive: true});
}
});
const onContextmenu = (ev) => {
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
};
if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection()?.toString() !== '') return;
const path = mainRouter.getCurrentPath();
os.contextMenu([{
type: 'label',
text: path,
}, {
icon: 'ti ti-window-maximize',
text: i18n.ts.openInWindow,
action: () => {
os.pageWindow(path);
},
}], ev);
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
};
if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection()?.toString() !== '') return;
const path = mainRouter.getCurrentPath();
os.contextMenu([{
type: 'label',
text: path,
}, {
icon: 'ti ti-window-maximize',
text: i18n.ts.openInWindow,
action: () => {
os.pageWindow(path);
},
}], ev);
};
function top() {
contents.value.rootEl.scrollTo({
top: 0,
behavior: 'smooth',
});
contents.value.rootEl.scrollTo({
top: 0,
behavior: 'smooth',
});
}
let navFooterHeight = $ref(0);
provide<Ref<number>>(CURRENT_STICKY_BOTTOM, $$(navFooterHeight));
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');
}
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,
immediate: true,
});
useScrollPositionManager(() => contents.value.rootEl, mainRouter);
@ -234,22 +275,22 @@ useScrollPositionManager(() => contents.value.rootEl, mainRouter);
<style>
html,
body {
width: 100%;
height: 100%;
overflow: clip;
position: fixed;
top: 0;
left: 0;
overscroll-behavior: none;
width: 100%;
height: 100%;
overflow: clip;
position: fixed;
top: 0;
left: 0;
overscroll-behavior: none;
}
#misskey_app {
width: 100%;
height: 100%;
overflow: clip;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: clip;
position: absolute;
top: 0;
left: 0;
}
</style>
@ -259,218 +300,222 @@ $widgets-hide-threshold: 1090px;
.transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawerBg_enterFrom,
.transition_menuDrawerBg_leaveTo {
opacity: 0;
opacity: 0;
}
.transition_menuDrawer_enterActive,
.transition_menuDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawer_enterFrom,
.transition_menuDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
opacity: 0;
transform: translateX(-240px);
}
.transition_widgetsDrawerBg_enterActive,
.transition_widgetsDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawerBg_enterFrom,
.transition_widgetsDrawerBg_leaveTo {
opacity: 0;
opacity: 0;
}
.transition_widgetsDrawer_enterActive,
.transition_widgetsDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawer_enterFrom,
.transition_widgetsDrawer_leaveTo {
opacity: 0;
transform: translateX(240px);
opacity: 0;
transform: translateX(240px);
}
.root {
height: 100dvh;
overflow: clip;
contain: strict;
box-sizing: border-box;
display: flex;
height: 100dvh;
overflow: clip;
contain: strict;
box-sizing: border-box;
display: flex;
}
.sidebar {
border-right: solid 0.5px var(--divider);
border-right: solid 0.5px var(--divider);
}
.contents {
flex: 1;
height: 100%;
min-width: 0;
overflow: auto;
overflow-y: scroll;
overscroll-behavior: contain;
background: var(--bg);
flex: 1;
height: 100%;
min-width: 0;
overflow: auto;
overflow-y: scroll;
overscroll-behavior: contain;
background: var(--bg);
}
.widgets {
width: 350px;
height: 100%;
box-sizing: border-box;
overflow: auto;
padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
border-left: solid 0.5px var(--divider);
background: var(--bg);
width: 350px;
height: 100%;
box-sizing: border-box;
overflow: auto;
padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
border-left: solid 0.5px var(--divider);
background: var(--bg);
@media (max-width: $widgets-hide-threshold) {
display: none;
}
@media (max-width: $widgets-hide-threshold) {
display: none;
}
}
.widgetButton {
display: block;
position: fixed;
z-index: 1000;
bottom: 32px;
right: 32px;
width: 64px;
height: 64px;
border-radius: 100%;
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
font-size: 22px;
background: var(--panel);
display: block;
position: fixed;
z-index: 1000;
bottom: 32px;
right: 32px;
width: 64px;
height: 64px;
border-radius: 100%;
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
font-size: 22px;
background: var(--panel);
}
.widgetsDrawerBg {
z-index: 1001;
z-index: 1001;
}
.widgetsDrawer {
position: fixed;
top: 0;
right: 0;
z-index: 1001;
width: 310px;
height: 100dvh;
padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--bg);
position: fixed;
top: 0;
right: 0;
z-index: 1001;
width: 310px;
height: 100dvh;
padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--bg);
}
.widgetsCloseButton {
padding: 8px;
display: block;
margin: 0 auto;
padding: 8px;
display: block;
margin: 0 auto;
}
@media (min-width: 370px) {
.widgetsCloseButton {
display: none;
}
.widgetsCloseButton {
display: none;
}
}
.nav {
position: fixed;
z-index: 1000;
bottom: 0;
left: 0;
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 8px;
width: 100%;
box-sizing: border-box;
-webkit-backdrop-filter: var(--blur, blur(24px));
backdrop-filter: var(--blur, blur(24px));
background-color: var(--header);
border-top: solid 0.5px var(--divider);
position: fixed;
z-index: 1000;
bottom: 0;
left: 0;
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 8px;
width: 100%;
box-sizing: border-box;
-webkit-backdrop-filter: var(--blur, blur(24px));
backdrop-filter: var(--blur, blur(24px));
background-color: var(--header);
border-top: solid 0.5px var(--divider);
}
.navButton {
position: relative;
padding: 0;
aspect-ratio: 1;
width: 100%;
max-width: 60px;
margin: auto;
border-radius: 100%;
background: var(--panel);
color: var(--fg);
position: relative;
padding: 0;
aspect-ratio: 1;
width: 100%;
max-width: 60px;
margin: auto;
border-radius: 100%;
background: var(--panel);
color: var(--fg);
&:hover {
background: var(--panelHighlight);
}
&:hover {
background: var(--panelHighlight);
}
&:active {
background: var(--X2);
}
&:active {
background: var(--X2);
}
}
.postButton {
composes: navButton;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
color: var(--fgOnAccent);
composes: navButton;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
color: var(--fgOnAccent);
&:hover {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
&:hover {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
&:active {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
&:active {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
}
.navButtonIcon {
font-size: 18px;
vertical-align: middle;
font-size: 18px;
vertical-align: middle;
}
.navButtonIndicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
.menuDrawerBg {
z-index: 1001;
z-index: 1001;
}
.menuDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
height: 100dvh;
width: 240px;
box-sizing: border-box;
contain: strict;
overflow: auto;
overscroll-behavior: contain;
background: var(--navBg);
position: fixed;
top: 0;
left: 0;
z-index: 1001;
height: 100dvh;
width: 240px;
box-sizing: border-box;
contain: strict;
overflow: auto;
overscroll-behavior: contain;
background: var(--navBg);
}
.statusbars {
position: sticky;
top: 0;
left: 0;
position: sticky;
top: 0;
left: 0;
}
.spacer {
height: calc(var(--minBottomSpacing));
height: calc(var(--minBottomSpacing));
}
</style>

View file

@ -1,7 +1,7 @@
import path from 'path';
import pluginReplace from '@rollup/plugin-replace';
import pluginVue from '@vitejs/plugin-vue';
import { type UserConfig, defineConfig } from 'vite';
import {type UserConfig, defineConfig} from 'vite';
// @ts-expect-error https://github.com/sxzz/unplugin-vue-macros/issues/257#issuecomment-1410752890
import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
@ -9,6 +9,7 @@ import locales from '../../locales';
import meta from '../../package.json';
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name';
import pluginJson5 from './vite.json5';
import compression from "vite-plugin-compression2";
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
@ -44,12 +45,12 @@ function toBase62(n: number): string {
export function getConfig(): UserConfig {
return {
base: '/vite/',
server: {
port: 5173,
},
plugins: [
compression({ algorithm: 'brotliCompress'}),
pluginVue({
reactivityTransform: true,
}),