Merge branch 'develop' into mkjs-n

This commit is contained in:
tamaina
2023-06-04 13:31:14 +00:00
127 changed files with 7780 additions and 3638 deletions

View File

@@ -3,7 +3,14 @@
<div v-if="achievements" :class="$style.root">
<div v-for="achievement in achievements" :key="achievement" :class="$style.achievement" class="_panel">
<div :class="$style.icon">
<div :class="[$style.iconFrame, $style['iconFrame_' + ACHIEVEMENT_BADGES[achievement.name].frame]]">
<div
:class="[$style.iconFrame, {
[$style.iconFrame_bronze]: ACHIEVEMENT_BADGES[achievement.name].frame === 'bronze',
[$style.iconFrame_silver]: ACHIEVEMENT_BADGES[achievement.name].frame === 'silver',
[$style.iconFrame_gold]: ACHIEVEMENT_BADGES[achievement.name].frame === 'gold',
[$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum',
}]"
>
<div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg }">
<img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img">
</div>

View File

@@ -10,7 +10,7 @@
</li>
<li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
</ol>
<ol v-else-if="hashtags.length > 0" ref="suggests" :class="[$style.list, $style.hashtags]">
<ol v-else-if="hashtags.length > 0" ref="suggests" :class="$style.list">
<li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown">
<span class="name">{{ hashtag }}</span>
</li>
@@ -42,7 +42,7 @@ import { acct } from '@/filters/user';
import * as os from '@/os';
import { MFM_TAGS } from '@/scripts/mfm-tags';
import { defaultStore } from '@/store';
import { emojilist } from '@/scripts/emojilist';
import { emojilist, getEmojiName } from '@/scripts/emojilist';
import { i18n } from '@/i18n';
import { miLocalStorage } from '@/local-storage';
import { customEmojis } from '@/custom-emojis';
@@ -71,14 +71,14 @@ const emojiDb = computed(() => {
url: char2path(x.char),
}));
for (const x of lib) {
if (x.keywords) {
for (const k of x.keywords) {
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const [emoji, keywords] of Object.entries(index)) {
for (const k of keywords) {
unicodeEmojiDB.push({
emoji: x.char,
emoji: emoji,
name: k,
aliasOf: x.name,
url: char2path(x.char),
aliasOf: getEmojiName(emoji)!,
url: char2path(emoji),
});
}
}

View File

@@ -7,7 +7,7 @@
@click="emit('click', $event)"
@mousedown="onMousedown"
>
<div ref="ripples" :class="$style.ripples"></div>
<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
<div :class="$style.content">
<slot></slot>
</div>
@@ -18,7 +18,7 @@
:to="to"
@mousedown="onMousedown"
>
<div ref="ripples" :class="$style.ripples"></div>
<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
<div :class="$style.content">
<slot></slot>
</div>
@@ -26,9 +26,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, useCssModule } from 'vue';
const $style = useCssModule();
import { nextTick, onMounted } from 'vue';
const props = defineProps<{
type?: 'button' | 'submit' | 'reset';
@@ -81,7 +79,7 @@ function onMousedown(evt: MouseEvent): void {
const rect = target.getBoundingClientRect();
const ripple = document.createElement('div');
ripple.classList.add($style.ripple);
ripple.classList.add(ripples!.dataset.childrenClass!);
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';

View File

@@ -1,20 +1,20 @@
<template>
<button
class="hdcaacmi _button"
:class="{ wait, active: isFollowing, full }"
class="_button"
:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing, [$style.full]: full }]"
:disabled="wait"
@click="onClick"
>
<template v-if="!wait">
<template v-if="isFollowing">
<span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
</template>
<template v-else>
<span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
</template>
</template>
<template v-else>
<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true"/>
<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true"/>
</template>
</button>
</template>
@@ -57,8 +57,8 @@ async function onClick() {
}
</script>
<style lang="scss" scoped>
.hdcaacmi {
<style lang="scss" module>
.root {
position: relative;
display: inline-block;
font-weight: bold;
@@ -103,7 +103,7 @@ async function onClick() {
}
&.active {
color: #fff;
color: var(--fgOnAccent);
background: var(--accent);
&:hover {
@@ -121,9 +121,9 @@ async function onClick() {
cursor: wait !important;
opacity: 0.7;
}
}
> span {
margin-right: 6px;
}
.text {
margin-right: 6px;
}
</style>

View File

@@ -3,7 +3,7 @@
<div v-if="game.ready" :class="$style.game">
<div :class="$style.cps" class="">{{ number(cps) }}cps</div>
<div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div>
<button v-click-anime class="_button" :class="$style.button" @click="onClick">
<button v-click-anime class="_button" @click="onClick">
<img src="/client-assets/cookie.png" :class="$style.img">
</button>
</div>
@@ -84,10 +84,6 @@ onUnmounted(() => {
margin-bottom: 6px;
}
.button {
}
.img {
max-width: 90px;
}

View File

@@ -1,5 +1,5 @@
<template>
<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]">
<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.scrollable]: scrollable }]">
<header v-if="showHeader" ref="headerEl" :class="$style.header">
<div :class="$style.title">
<span :class="$style.titleIcon"><slot name="icon"></slot></span>
@@ -34,7 +34,7 @@
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef, watch } from 'vue';
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
@@ -83,13 +83,19 @@ function afterLeave(el) {
const calcOmit = () => {
if (omitted.value || ignoreOmit.value || props.maxHeight == null) return;
if (!contentEl.value) return;
const height = contentEl.value.offsetHeight;
omitted.value = height > props.maxHeight;
};
const omitObserver = new ResizeObserver((entries, observer) => {
calcOmit();
});
onMounted(() => {
watch(showBody, v => {
const headerHeight = props.showHeader ? headerEl.value.offsetHeight : 0;
if (!rootEl.value) return;
const headerHeight = props.showHeader ? headerEl.value?.offsetHeight ?? 0 : 0;
rootEl.value.style.minHeight = `${headerHeight}px`;
if (v) {
rootEl.value.style.flexBasis = 'auto';
@@ -100,13 +106,15 @@ onMounted(() => {
immediate: true,
});
rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
if (rootEl.value) rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
calcOmit();
new ResizeObserver((entries, observer) => {
calcOmit();
}).observe(contentEl.value);
if (contentEl.value) omitObserver.observe(contentEl.value);
});
onUnmounted(() => {
omitObserver.disconnect();
});
</script>

View File

@@ -36,7 +36,7 @@ export default defineComponent({
},
setup(props, { slots, expose }) {
const $style = useCssModule();
const $style = useCssModule(); // カスタムレンダラなので使っても大丈夫
function getDateText(time: string) {
const date = new Date(time).getDate();
const month = new Date(time).getMonth() + 1;

View File

@@ -4,7 +4,15 @@
<div v-if="icon" :class="$style.icon">
<i :class="icon"></i>
</div>
<div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]">
<div
v-else-if="!input && !select"
:class="[$style.icon, {
[$style.type_success]: type === 'success',
[$style.type_error]: type === 'error',
[$style.type_warning]: type === 'warning',
[$style.type_info]: type === 'info',
}]"
>
<i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i>
<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>

View File

@@ -1,7 +1,8 @@
<template>
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter">
<div ref="emojisEl" class="emojis">
<!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 -->
<div ref="emojisEl" class="emojis" tabindex="-1">
<section class="result">
<div v-if="searchResultCustom.length > 0" class="body">
<button
@@ -101,7 +102,7 @@ import { isTouchUsing } from '@/scripts/touch';
import { deviceKind } from '@/scripts/device-kind';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { customEmojiCategories, customEmojis } from '@/custom-emojis';
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis';
import { $i } from '@/account';
const props = withDefaults(defineProps<{
@@ -223,7 +224,6 @@ watch(q, () => {
if (newQ.includes(' ')) { // AND検索
const keywords = newQ.split(' ');
// 名前にキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
@@ -232,11 +232,12 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
// 名前またはエイリアスにキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
} else {
@@ -248,13 +249,14 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (index[emoji.char].some(k => k.startsWith(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(newQ)) {
@@ -264,10 +266,12 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.includes(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (index[emoji.char].some(k => k.includes(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
}
@@ -352,7 +356,7 @@ function done(query?: string): boolean | void {
if (query == null || typeof query !== 'string') return;
const q2 = query.replace(/:/g, '');
const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2);
const exactMatchCustom = customEmojisMap.get(q2);
if (exactMatchCustom) {
chosen(exactMatchCustom);
return true;

View File

@@ -5,7 +5,7 @@
<div :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle">
<div :class="$style.headerIcon"><slot name="icon"></slot></div>
<div :class="$style.headerText">
<div :class="$style.headerTextMain">
<div>
<MkCondensedLine :minScale="2 / 3"><slot name="label"></slot></MkCondensedLine>
</div>
<div :class="$style.headerTextSub">
@@ -185,10 +185,6 @@ onMounted(() => {
padding-right: 12px;
}
.headerTextMain {
}
.headerTextSub {
color: var(--fgTransparentWeak);
font-size: .85em;

View File

@@ -1,30 +1,30 @@
<template>
<button
class="kpoogebi _button"
:class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }"
class="_button"
:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
:disabled="wait"
@click="onClick"
>
<template v-if="!wait">
<template v-if="hasPendingFollowRequestFromYou && user.isLocked">
<span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i>
</template>
<template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked">
<!-- つまりリモートフォローの場合 -->
<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
</template>
<template v-else-if="isFollowing">
<span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
</template>
<template v-else-if="!isFollowing && user.isLocked">
<span v-if="full">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i>
</template>
<template v-else-if="!isFollowing && !user.isLocked">
<span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
<span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
</template>
</template>
<template v-else>
<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
</template>
</button>
</template>
@@ -126,13 +126,12 @@ onBeforeUnmount(() => {
});
</script>
<style lang="scss" scoped>
.kpoogebi {
<style lang="scss" module>
.root {
position: relative;
display: inline-block;
font-weight: bold;
color: var(--accent);
background: transparent;
color: var(--fgOnWhite);
border: solid 1px var(--accent);
padding: 0;
height: 31px;
@@ -196,9 +195,9 @@ onBeforeUnmount(() => {
cursor: wait !important;
opacity: 0.7;
}
}
> span {
margin-right: 6px;
}
.text {
margin-right: 6px;
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<div ref="root" :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''">
<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
<TransitionGroup
:duration="defaultStore.state.animation && props.transition?.duration || undefined"
:enterActiveClass="defaultStore.state.animation && props.transition?.enterActiveClass || undefined"
:leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_leaveActive']) || undefined"
:leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
:enterFromClass="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
:leaveToClass="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
:enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined"
@@ -23,6 +23,11 @@ import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
// テスト環境で Web Worker インスタンスは作成できない
if (import.meta.env.MODE === 'test') {
resolve(null);
return;
}
const testWorker = new TestWebGL2();
testWorker.addEventListener('message', event => {
if (event.data.result) {
@@ -42,11 +47,10 @@ const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
</script>
<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, shallowRef, useCssModule, watch } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha';
import { defaultStore } from '@/store';
const $style = useCssModule();
const props = withDefaults(defineProps<{
transition?: {

View File

@@ -1,27 +1,27 @@
<template>
<div class="mk-media-banner">
<div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false">
<span class="icon"><i class="ti ti-alert-triangle"></i></span>
<div :class="$style.root">
<div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="hide = false">
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
<b>{{ i18n.ts.sensitive }}</b>
<span>{{ i18n.ts.clickToShow }}</span>
</div>
<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio">
<VuePlyr :options="{ volume: 0.5 }">
<audio controls preload="metadata">
<source
:src="media.url"
:type="media.type"
/>
</audio>
</VuePlyr>
<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :class="$style.audio">
<audio
ref="audioEl"
:src="media.url"
:title="media.name"
controls
preload="metadata"
@volumechange="volumechange"
/>
</div>
<a
v-else class="download"
v-else :class="$style.download"
:href="media.url"
:title="media.name"
:download="media.name"
>
<span class="icon"><i class="ti ti-download"></i></span>
<span style="font-size: 1.6em;"><i class="ti ti-download"></i></span>
<b>{{ media.name }}</b>
</a>
</div>
@@ -30,9 +30,7 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import * as misskey from 'misskey-js';
import VuePlyr from 'vue-plyr';
import { soundConfigStore } from '@/scripts/sound';
import 'vue-plyr/dist/vue-plyr.css';
import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
@@ -52,55 +50,34 @@ onMounted(() => {
});
</script>
<style lang="scss" scoped>
.mk-media-banner {
<style lang="scss" module>
.root {
width: 100%;
border-radius: 4px;
margin-top: 4px;
// overflow: clip;
overflow: clip;
}
--plyr-color-main: var(--accent);
--plyr-audio-controls-background: var(--bg);
--plyr-audio-controls-color: var(--accentLighten);
.download,
.sensitive {
display: flex;
align-items: center;
font-size: 12px;
padding: 8px 12px;
white-space: nowrap;
}
> .download,
> .sensitive {
display: flex;
align-items: center;
font-size: 12px;
padding: 8px 12px;
white-space: nowrap;
.download {
background: var(--noteAttachedFile);
}
> * {
display: block;
}
.sensitive {
background: #111;
color: #fff;
}
> b {
overflow: hidden;
text-overflow: ellipsis;
}
> *:not(:last-child) {
margin-right: .2em;
}
> .icon {
font-size: 1.6em;
}
}
> .download {
background: var(--noteAttachedFile);
}
> .sensitive {
background: #111;
color: #fff;
}
> .audio {
border-radius: 8px;
// overflow: clip;
}
.audio {
border-radius: 8px;
overflow: clip;
}
</style>

View File

@@ -32,8 +32,7 @@
<div v-if="image.comment" :class="$style.indicator">ALT</div>
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div>
</div>
<button v-tooltip="i18n.ts.hide" :class="$style.hide" class="_button" @click.stop.prevent="hide = true"><i class="ti ti-eye-off"></i></button>
<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots"></i></button>
<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button>
</template>
</div>
</template>
@@ -79,9 +78,16 @@ watch(() => props.image, () => {
});
function showMenu(ev: MouseEvent) {
os.popupMenu([...(iAmModerator ? [{
text: i18n.ts.markAsSensitive,
os.popupMenu([{
text: i18n.ts.hide,
icon: 'ti ti-eye-off',
action: () => {
hide = true;
},
}, ...(iAmModerator ? [{
text: i18n.ts.markAsSensitive,
icon: 'ti ti-eye-exclamation',
danger: true,
action: () => {
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
},
@@ -122,34 +128,20 @@ function showMenu(ev: MouseEvent) {
background-size: 16px 16px;
}
.hide {
display: block;
position: absolute;
border-radius: 6px;
background-color: var(--accentedBg);
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
color: var(--accent);
font-size: 0.8em;
padding: 6px 8px;
text-align: center;
top: 12px;
right: 12px;
}
.menu {
display: block;
position: absolute;
border-radius: 6px;
border-radius: 999px;
background-color: rgba(0, 0, 0, 0.3);
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
color: #fff;
font-size: 0.8em;
padding: 6px 8px;
width: 32px;
height: 32px;
text-align: center;
bottom: 12px;
right: 12px;
bottom: 10px;
right: 10px;
}
.imageContainer {
@@ -166,12 +158,10 @@ function showMenu(ev: MouseEvent) {
.indicators {
display: inline-flex;
position: absolute;
top: 12px;
left: 12px;
text-align: center;
top: 10px;
left: 10px;
pointer-events: none;
opacity: .5;
font-size: 14px;
gap: 6px;
}
@@ -182,7 +172,7 @@ function showMenu(ev: MouseEvent) {
color: var(--accentLighten);
display: inline-block;
font-weight: bold;
font-size: 12px;
padding: 2px 6px;
font-size: 0.8em;
padding: 2px 5px;
}
</style>

View File

@@ -6,8 +6,11 @@
ref="gallery"
:class="[
$style.medias,
count <= 4 ? $style['n' + count] : $style.nMany,
$style[`n1${defaultStore.reactiveState.mediaListWithOneImageAppearance.value}`]
count === 1 ? [$style.n1, {
[$style.n116_9]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '16_9',
[$style.n11_1]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '1_1',
[$style.n12_3]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '2_3',
}] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany,
]"
>
<template v-for="media in mediaList.filter(media => previewable(media))">
@@ -20,7 +23,7 @@
</template>
<script lang="ts" setup>
import { onMounted, ref, useCssModule, watch, shallowRef } from 'vue';
import { onMounted, watch, shallowRef } from 'vue';
import * as misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe';
@@ -37,8 +40,6 @@ const props = defineProps<{
raw?: boolean;
}>();
const $style = useCssModule();
const gallery = shallowRef<HTMLDivElement>();
const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
@@ -96,7 +97,7 @@ onMounted(() => {
return item;
}),
gallery: gallery.value,
mainClass: $style.pswp,
mainClass: 'pswp',
children: '.image',
thumbSelector: '.image',
loop: false,
@@ -268,7 +269,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
border-radius: 8px;
}
.pswp {
:global(.pswp) {
--pswp-root-z-index: var(--mk-pswp-root-z-index, 2000700) !important;
--pswp-bg: var(--modalBg) !important;
}

View File

@@ -1,36 +1,36 @@
<template>
<div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false">
<div v-if="hide" :class="$style.hidden" @click="hide = false">
<!-- 注意dataSaverMode が有効になっている際にはhide false になるまでサムネイルや動画を読み込まないようにすること -->
<div>
<b v-if="video.isSensitive"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
<b v-else><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<div :class="$style.sensitive">
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<span>{{ i18n.ts.clickToShow }}</span>
</div>
</div>
<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
<VuePlyr :options="{ volume: 0.5 }">
<video
controls
:data-poster="video.thumbnailUrl"
<div v-else :class="$style.visible">
<video
:class="$style.video"
:poster="video.thumbnailUrl"
:title="video.comment"
:alt="video.comment"
preload="none"
controls
@contextmenu.stop
>
<source
:src="video.url"
:type="video.type"
>
<source
size="720"
:src="video.url"
:type="video.type"
/>
</video>
</VuePlyr>
<i class="ti ti-eye-off" @click="hide = true"></i>
</video>
<i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import VuePlyr from 'vue-plyr';
import bytes from '@/filters/bytes';
import { defaultStore } from '@/store';
import 'vue-plyr/dist/vue-plyr.css';
import { i18n } from '@/i18n';
const props = defineProps<{
@@ -40,56 +40,49 @@ const props = defineProps<{
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
</script>
<style lang="scss" scoped>
.kkjnbbplepmiyuadieoenjgutgcmtsvu {
<style lang="scss" module>
.visible {
position: relative;
--plyr-color-main: var(--accent);
> i {
display: block;
position: absolute;
border-radius: 6px;
background-color: var(--fg);
color: var(--accentLighten);
font-size: 14px;
opacity: .5;
padding: 3px 6px;
text-align: center;
cursor: pointer;
top: 12px;
right: 12px;
}
> video {
display: flex;
justify-content: center;
align-items: center;
font-size: 3.5em;
overflow: hidden;
background-position: center;
background-size: cover;
width: 100%;
height: 100%;
}
}
.icozogqfvdetwohsdglrbswgrejoxbdj {
.hide {
display: block;
position: absolute;
border-radius: 6px;
background-color: var(--fg);
color: var(--accentLighten);
font-size: 14px;
opacity: .5;
padding: 3px 6px;
text-align: center;
cursor: pointer;
top: 12px;
right: 12px;
}
.video {
display: flex;
justify-content: center;
align-items: center;
font-size: 3.5em;
overflow: hidden;
background-position: center;
background-size: cover;
width: 100%;
height: 100%;
}
.hidden {
display: flex;
justify-content: center;
align-items: center;
background: #111;
color: #fff;
}
> div {
display: table-cell;
text-align: center;
font-size: 12px;
> b {
display: block;
}
}
.sensitive {
display: table-cell;
text-align: center;
font-size: 12px;
}
</style>

View File

@@ -2,7 +2,7 @@
<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }">
<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
<span>
<span :class="$style.username">@{{ username }}</span>
<span>@{{ username }}</span>
<span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
</span>
</MkA>

View File

@@ -49,7 +49,7 @@
<span>{{ i18n.ts.none }}</span>
</span>
</div>
<div v-if="childMenu" :class="$style.child">
<div v-if="childMenu">
<XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned"/>
</div>
</div>

View File

@@ -1,10 +1,30 @@
<template>
<Transition
:name="transitionName"
:enterActiveClass="$style['transition_' + transitionName + '_enterActive']"
:leaveActiveClass="$style['transition_' + transitionName + '_leaveActive']"
:enterFromClass="$style['transition_' + transitionName + '_enterFrom']"
:leaveToClass="$style['transition_' + transitionName + '_leaveTo']"
:enterActiveClass="normalizeClass({
[$style.transition_modalDrawer_enterActive]: transitionName === 'modal-drawer',
[$style.transition_modalPopup_enterActive]: transitionName === 'modal-popup',
[$style.transition_modal_enterActive]: transitionName === 'modal',
[$style.transition_send_enterActive]: transitionName === 'send',
})"
:leaveActiveClass="normalizeClass({
[$style.transition_modalDrawer_leaveActive]: transitionName === 'modal-drawer',
[$style.transition_modalPopup_leaveActive]: transitionName === 'modal-popup',
[$style.transition_modal_leaveActive]: transitionName === 'modal',
[$style.transition_send_leaveActive]: transitionName === 'send',
})"
:enterFromClass="normalizeClass({
[$style.transition_modalDrawer_enterFrom]: transitionName === 'modal-drawer',
[$style.transition_modalPopup_enterFrom]: transitionName === 'modal-popup',
[$style.transition_modal_enterFrom]: transitionName === 'modal',
[$style.transition_send_enterFrom]: transitionName === 'send',
})"
:leaveToClass="normalizeClass({
[$style.transition_modalDrawer_leaveTo]: transitionName === 'modal-drawer',
[$style.transition_modalPopup_leaveTo]: transitionName === 'modal-popup',
[$style.transition_modal_leaveTo]: transitionName === 'modal',
[$style.transition_send_leaveTo]: transitionName === 'send',
})"
:duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened"
>
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
@@ -17,7 +37,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, watch, provide } from 'vue';
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue';
import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch';
import { defaultStore } from '@/store';
@@ -38,7 +58,7 @@ type ModalTypes = 'popup' | 'dialog' | 'drawer';
const props = withDefaults(defineProps<{
manualShowing?: boolean | null;
anchor?: { x: string; y: string; };
src?: HTMLElement;
src?: HTMLElement | null;
preferType?: ModalTypes | 'auto';
zPriority?: 'low' | 'middle' | 'high';
noOverlap?: boolean;
@@ -264,6 +284,10 @@ const onOpened = () => {
}, { passive: true });
};
const alignObserver = new ResizeObserver((entries, observer) => {
align();
});
onMounted(() => {
watch(() => props.src, async () => {
if (props.src) {
@@ -278,12 +302,14 @@ onMounted(() => {
}, { immediate: true });
nextTick(() => {
new ResizeObserver((entries, observer) => {
align();
}).observe(content!);
alignObserver.observe(content!);
});
});
onUnmounted(() => {
alignObserver.disconnect();
});
defineExpose({
close,
});
@@ -339,8 +365,8 @@ defineExpose({
}
}
.transition_modal-popup_enterActive,
.transition_modal-popup_leaveActive {
.transition_modalPopup_enterActive,
.transition_modalPopup_leaveActive {
> .bg {
transition: opacity 0.1s !important;
}
@@ -350,8 +376,8 @@ defineExpose({
transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important;
}
}
.transition_modal-popup_enterFrom,
.transition_modal-popup_leaveTo {
.transition_modalPopup_enterFrom,
.transition_modalPopup_leaveTo {
> .bg {
opacity: 0;
}
@@ -364,7 +390,7 @@ defineExpose({
}
}
.transition_modal-drawer_enterActive {
.transition_modalDrawer_enterActive {
> .bg {
transition: opacity 0.2s !important;
}
@@ -373,7 +399,7 @@ defineExpose({
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
}
}
.transition_modal-drawer_leaveActive {
.transition_modalDrawer_leaveActive {
> .bg {
transition: opacity 0.2s !important;
}
@@ -382,8 +408,8 @@ defineExpose({
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
}
}
.transition_modal-drawer_enterFrom,
.transition_modal-drawer_leaveTo {
.transition_modalDrawer_enterFrom,
.transition_modalDrawer_leaveTo {
> .bg {
opacity: 0;
}

View File

@@ -44,8 +44,8 @@
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/>
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
<MkNoteHeader :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
<p v-if="appearNote.cw != null" :class="$style.cw">
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
@@ -58,13 +58,13 @@
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else :class="$style.translated">
<div v-else>
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
</div>
</div>
</div>
<div v-if="appearNote.files.length > 0" :class="$style.files">
<div v-if="appearNote.files.length > 0">
<MkMediaList :mediaList="appearNote.files"/>
</div>
<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/>
@@ -205,8 +205,11 @@ const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
const isLong = (appearNote.cw == null && appearNote.text != null && (
(appearNote.text.includes('$[x2')) ||
(appearNote.text.includes('$[x3')) ||
(appearNote.text.includes('$[x4')) ||
(appearNote.text.includes('$[scale')) ||
(appearNote.text.includes('$[position')) ||
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500) ||
(appearNote.files.length >= 5) ||
@@ -274,7 +277,7 @@ function renote(viaKeyboard = false) {
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
os.api('notes/create', {
renoteId: appearNote.id,
channelId: appearNote.channelId,
@@ -305,7 +308,7 @@ function renote(viaKeyboard = false) {
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
os.api('notes/create', {
renoteId: appearNote.id,
}).then(() => {
@@ -379,6 +382,8 @@ function undoReact(note): void {
function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
if (el.tagName === 'AUDIO') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}

View File

@@ -4,7 +4,7 @@
v-show="!isDeleted"
ref="el"
v-hotkey="keymap"
:class="[$style.root, { [$style.renote]: isRenote }]"
:class="$style.root"
>
<MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
@@ -52,7 +52,7 @@
</div>
</div>
<div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div>
<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
</div>
</header>
<div :class="$style.noteContent">
@@ -72,7 +72,7 @@
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
</div>
</div>
<div v-if="appearNote.files.length > 0" :class="$style.files">
<div v-if="appearNote.files.length > 0">
<MkMediaList :mediaList="appearNote.files"/>
</div>
<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>

View File

@@ -6,7 +6,7 @@
<MkUserName :user="$i" :nowrap="true"/>
</div>
<div>
<div :class="$style.content">
<div>
<Mfm :text="text.trim()" :author="$i" :i="$i"/>
</div>
</div>

View File

@@ -5,7 +5,19 @@
<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
<div :class="[$style.subIcon, $style['t_' + notification.type]]">
<div
:class="[$style.subIcon, {
[$style.t_follow]: notification.type === 'follow',
[$style.t_followRequestAccepted]: notification.type === 'followRequestAccepted',
[$style.t_receiveFollowRequest]: notification.type === 'receiveFollowRequest',
[$style.t_renote]: notification.type === 'renote',
[$style.t_reply]: notification.type === 'reply',
[$style.t_mention]: notification.type === 'mention',
[$style.t_quote]: notification.type === 'quote',
[$style.t_pollEnded]: notification.type === 'pollEnded',
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
}]"
>
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
@@ -34,7 +46,7 @@
<span v-else>{{ notification.header }}</span>
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
</header>
<div :class="$style.content">
<div>
<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote" :class="$style.quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
@@ -243,9 +255,6 @@ useTooltip(reactionRef, (showing) => {
font-size: 0.9em;
}
.content {
}
.text {
display: flex;
width: 100%;

View File

@@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, onUnmounted } from 'vue';
import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
@@ -21,16 +21,22 @@ let content = $shallowRef<HTMLElement>();
let omitted = $ref(false);
let ignoreOmit = $ref(false);
onMounted(() => {
const calcOmit = () => {
if (omitted || ignoreOmit) return;
omitted = content.offsetHeight > props.maxHeight;
};
const calcOmit = () => {
if (omitted || ignoreOmit) return;
omitted = content.offsetHeight > props.maxHeight;
};
const omitObserver = new ResizeObserver((entries, observer) => {
calcOmit();
new ResizeObserver((entries, observer) => {
calcOmit();
}).observe(content);
});
onMounted(() => {
calcOmit();
omitObserver.observe(content);
});
onUnmounted(() => {
omitObserver.disconnect();
});
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div :class="{ [$style.done]: closed || isVoted }">
<ul :class="$style.choices">
<li v-for="(choice, i) in note.poll.choices" :key="i" :class="[$style.choice, { [$style.voted]: choice.voted }]" @click="vote(i)">
<li v-for="(choice, i) in note.poll.choices" :key="i" :class="$style.choice" @click="vote(i)">
<div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span :class="$style.fg">
<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template>

View File

@@ -22,21 +22,21 @@
<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
<span :class="$style.headerRightButtonText">{{ i18n.ts._visibility[visibility] }}</span>
</button>
<button v-else :class="['_button', $style.headerRightItem, $style.visibility]" disabled>
<button v-else class="_button" :class="[$style.headerRightItem, $style.visibility]" disabled>
<span><i class="ti ti-device-tv"></i></span>
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
</button>
</template>
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" :class="['_button', $style.headerRightItem, $style.localOnly, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
<span v-else><i class="ti ti-rocket-off"></i></span>
</button>
<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" :class="['_button', $style.headerRightItem, $style.reactionAcceptance, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
<span v-else><i class="ti ti-icons"></i></span>
</button>
<button v-click-anime class="_button" :class="[$style.submit, { [$style.submitPosting]: posting }]" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
<div :class="$style.submitInner">
<template v-if="posted"></template>
<template v-else-if="posting"><MkEllipsis/></template>
@@ -66,7 +66,7 @@
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
<div v-if="showingOptions" style="padding: 8px 16px;">

View File

@@ -93,7 +93,7 @@ function showFileMenu(file, ev: MouseEvent) {
action: () => { rename(file); },
}, {
text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: file.isSensitive ? 'ti ti-eye-off' : 'ti ti-eye',
icon: file.isSensitive ? 'ti ti-eye-exclamation' : 'ti ti-eye',
action: () => { toggleSensitive(file); },
}, {
text: i18n.ts.describeFile,

View File

@@ -1,6 +1,6 @@
<template>
<div :class="[$style.root, { [$style.collapsed]: collapsed }]">
<div :class="$style.body">
<div>
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
@@ -76,10 +76,6 @@ const collapsed = $ref(
}
}
.body {
}
.reply {
margin-right: 6px;
color: var(--accent);

View File

@@ -6,7 +6,7 @@
ref="inputEl"
v-model="v"
v-adaptive-border
:class="[$style.textarea, { [$style.code]: code, _monospace: code }]"
:class="[$style.textarea, { _monospace: code }]"
:disabled="disabled"
:required="required"
:readonly="readonly"

View File

@@ -22,7 +22,7 @@
</div>
</template>
<template v-else-if="tweetId && tweetExpanded">
<div ref="twitter" :class="$style.twitter">
<div ref="twitter">
<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
</div>
<div :class="$style.action">
@@ -31,7 +31,7 @@
</MkButton>
</div>
</template>
<div v-else :class="$style.urlPreview">
<div v-else>
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`">
</div>
@@ -210,13 +210,6 @@ onUnmounted(() => {
width: 100%;
}
.twitter {
}
.urlPreview {
}
.link {
position: relative;
display: block;

View File

@@ -8,7 +8,7 @@
</div>
<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
<div :class="$style.description">
<div v-if="user.description" class="mfm">
<div v-if="user.description" :class="$style.mfm">
<Mfm :text="user.description" :author="user" :i="$i"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>

View File

@@ -1,5 +1,13 @@
<template>
<div v-tooltip="text" :class="[$style.root, $style['status_' + user.onlineStatus]]"></div>
<div
v-tooltip="text"
:class="[$style.root, {
[$style.status_online]: user.onlineStatus === 'online',
[$style.status_active]: user.onlineStatus === 'active',
[$style.status_offline]: user.onlineStatus === 'offline',
[$style.status_unknown]: user.onlineStatus === 'unknown',
}]"
></div>
</template>
<script lang="ts" setup>

View File

@@ -9,7 +9,7 @@
@closed="$emit('closed')"
>
<template #header>{{ i18n.ts.selectUser }}</template>
<div :class="$style.root">
<div>
<div :class="$style.form">
<FormSplit :minWidth="170">
<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
@@ -126,8 +126,6 @@ onMounted(() => {
</script>
<style lang="scss" module>
.root {
}
.form {
padding: 0 var(--root-margin);

View File

@@ -3,9 +3,9 @@
<div :class="$style.root">
<div v-for="u in users" :key="u.id" :class="$style.user">
<MkAvatar :class="$style.avatar" :user="u"/>
<MkUserName :class="$style.name" :user="u" :nowrap="true"/>
<MkUserName :user="u" :nowrap="true"/>
</div>
<div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div>
<div v-if="users.length < count">+{{ count - users.length }}</div>
</div>
</MkTooltip>
</template>
@@ -43,14 +43,6 @@ const emit = defineEmits<{
}
}
.name {
}
.omitted {
}
.avatar {
width: 24px;
height: 24px;

View File

@@ -39,7 +39,7 @@
<MkTimeline src="local"/>
</div>
</div>
<div :class="[$style.activeUsersChart, $style.panel]">
<div :class="$style.panel">
<XActiveUsersChart/>
</div>
</div>
@@ -220,8 +220,4 @@ function exploreOtherServers() {
height: 350px;
overflow: auto;
}
.activeUsersChart {
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div :class="$style.root">
<template v-if="edit">
<header :class="$style['edit-header']">
<header :class="$style.editHeader">
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
<template #label>{{ i18n.ts.selectWidget }}</template>
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
@@ -15,15 +15,15 @@
handle=".handle"
:animation="150"
:group="{ name: 'SortableMkWidgets' }"
:class="$style['edit-editing']"
:class="$style.editEditing"
@update:modelValue="v => emit('updateWidgets', v)"
>
<template #item="{element}">
<div :class="[$style.widget, $style['customize-container']]" data-cy-customize-container>
<button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
<button :class="$style['customize-container-remove']" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
<div :class="[$style.widget, $style.customizeContainer]" data-cy-customize-container>
<button :class="$style.customizeContainerConfig" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
<button :class="$style.customizeContainerRemove" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
<div class="handle">
<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style.customizeContainerHandleWidget" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
</div>
</div>
</template>
@@ -130,7 +130,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
}
.edit {
&-header {
&Header {
margin: 16px 0;
> * {
@@ -139,17 +139,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
}
}
&-editing {
&Editing {
min-height: 100px;
}
}
.customize-container {
.customizeContainer {
position: relative;
cursor: move;
&-config,
&-remove {
&Config,
&Remove {
position: absolute;
z-index: 10000;
top: 8px;
@@ -160,17 +160,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
border-radius: 4px;
}
&-config {
&Config {
right: 8px + 8px + 32px;
}
&-remove {
&Remove {
right: 8px;
}
&-handle {
&Handle {
&-widget {
&Widget {
pointer-events: none;
}
}

View File

@@ -5,7 +5,7 @@
<span :class="$style.text"><slot></slot></span>
<span :class="$style.suffix">
<span :class="$style.suffixText"><slot name="suffix"></slot></span>
<i class="ti ti-external-link" :class="$style.suffixIcon"></i>
<i class="ti ti-external-link"></i>
</span>
</a>
<MkA v-else :class="[$style.main, { [$style.active]: active }]" class="_button" :to="to" :behavior="behavior">
@@ -13,7 +13,7 @@
<span :class="$style.text"><slot></slot></span>
<span :class="$style.suffix">
<span :class="$style.suffixText"><slot name="suffix"></slot></span>
<i class="ti ti-chevron-right" :class="$style.suffixIcon"></i>
<i class="ti ti-chevron-right"></i>
</span>
</MkA>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="$style.content">
<div>
<slot></slot>
</div>
<div :class="$style.caption"><slot name="caption"></slot></div>

View File

@@ -1,6 +1,14 @@
<template>
<div v-if="chosen && !shouldHide" :class="$style.root">
<div v-if="!showMenu" :class="[$style.main, $style['form_' + chosen.place]]">
<div
v-if="!showMenu"
:class="[$style.main, {
[$style.form_square]: chosen.place === 'square',
[$style.form_horizontal]: chosen.place === 'horizontal',
[$style.form_horizontalBig]: chosen.place === 'horizontal-big',
[$style.form_vertical]: chosen.place === 'vertical',
}]"
>
<a :href="chosen.url" target="_blank" :class="$style.link">
<img :src="chosen.imageUrl" :class="$style.img">
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
@@ -122,7 +130,7 @@ function reduceFrequency(): void {
}
}
&.form_horizontal-big {
&.form_horizontalBig {
padding: 8px;
> .link,

View File

@@ -7,7 +7,7 @@
import { computed } from 'vue';
import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy';
import { defaultStore } from '@/store';
import { customEmojis } from '@/custom-emojis';
import { customEmojisMap } from '@/custom-emojis';
const props = defineProps<{
name: string;
@@ -26,7 +26,7 @@ const rawUrl = computed(() => {
return props.url;
}
if (isLocal.value) {
return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null;
return customEmojisMap.get(customEmojiName.value)?.url ?? null;
}
return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
});

View File

@@ -14,6 +14,7 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue';
import { $$ } from 'vue/macros';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const';
const rootEl = $shallowRef<HTMLElement>();
@@ -83,8 +84,8 @@ onMounted(() => {
onUnmounted(() => {
observer.disconnect();
});
defineExpose({
rootEl: $$(rootEl),
});
</script>
<style lang="scss" module>
</style>

View File

@@ -6,7 +6,7 @@
<template v-if="!self">
<span :class="$style.schema">{{ schema }}//</span>
<span :class="$style.hostname">{{ hostname }}</span>
<span v-if="port != ''" :class="$style.port">:{{ port }}</span>
<span v-if="port != ''">:{{ port }}</span>
</template>
<template v-if="pathname === '/' && self">
<span :class="$style.self">{{ hostname }}</span>

View File

@@ -1,5 +1,5 @@
<template>
<component :is="'x-' + block.type" :key="block.id" :page="page" :block="block" :h="h"/>
<component :is="getComponent(block.type)" :key="block.id" :page="page" :block="block" :h="h"/>
</template>
<script lang="ts" setup>
@@ -11,6 +11,16 @@ import XImage from './page.image.vue';
import XNote from './page.note.vue';
import { Block } from './block.type';
function getComponent(type: string) {
switch (type) {
case 'text': return XText;
case 'section': return XSection;
case 'image': return XImage;
case 'note': return XNote;
default: return null;
}
}
defineProps<{
block: Block,
h: number,

View File

@@ -1,6 +1,15 @@
<template>
<section>
<component :is="'h' + h" :class="h < 5 ? $style['h' + h] : null">{{ block.title }}</component>
<component
:is="'h' + h"
:class="{
'h2': h === 2,
'h3': h === 3,
'h4': h === 4,
}"
>
{{ block.title }}
</component>
<div class="_gaps">
<XBlock v-for="child in block.children" :key="child.id" :page="page" :block="child" :h="h + 1"/>

View File

@@ -1,6 +1,6 @@
<template>
<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }">
<XBlock v-for="child in page.content" :key="child.id" :block="child" :h="2"/>
<XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/>
</div>
</template>