Merge remote-tracking branch 'prismisskey/develop' into prismisskey
This commit is contained in:
commit
b853625500
34 changed files with 38058 additions and 342 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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?: {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
46
packages/frontend/src/components/global/MkEmojiKitchen.vue
Normal file
46
packages/frontend/src/components/global/MkEmojiKitchen.vue
Normal 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>
|
||||
|
|
@ -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), ']']);
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
37031
packages/frontend/src/scripts/emojiKitchen/emojiData.ts
Normal file
37031
packages/frontend/src/scripts/emojiKitchen/emojiData.ts
Normal file
File diff suppressed because it is too large
Load diff
67
packages/frontend/src/scripts/emojiKitchen/emojiMixer.ts
Normal file
67
packages/frontend/src/scripts/emojiKitchen/emojiMixer.ts
Normal 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();
|
||||
};
|
||||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue