Merge branch 'develop' into mkjs-n
This commit is contained in:
commit
c0cbbe6072
268 changed files with 4840 additions and 5205 deletions
|
|
@ -86,8 +86,13 @@
|
|||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label>Special thanks</template>
|
||||
<div style="text-align: center;">
|
||||
<a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a>
|
||||
<div class="_gaps" style="text-align: center;">
|
||||
<div>
|
||||
<a style="display: inline-block;" class="masknetwork" title="Mask Network" href="https://mask.io/" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/masknetwork.png" alt="Mask Network"></a>
|
||||
</div>
|
||||
<div>
|
||||
<a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="900">
|
||||
<div class="lcixvhis">
|
||||
<div>
|
||||
<div class="reports">
|
||||
<div class="">
|
||||
<div class="inputs" style="display: flex;">
|
||||
|
|
@ -87,9 +87,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-exclamation-circle',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lcixvhis {
|
||||
margin: var(--margin);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="900">
|
||||
<div class="ztgjmzrw _gaps_m">
|
||||
<div class="_gaps_m">
|
||||
<section v-for="announcement in announcements" class="">
|
||||
<div class="_panel _gaps_m" style="padding: 24px;">
|
||||
<MkInput v-model="announcement.title">
|
||||
|
|
@ -113,9 +113,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-speakerphone',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ztgjmzrw {
|
||||
margin: var(--margin);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions"/></template>
|
||||
<MkSpacer :content-max="900">
|
||||
<div class="xrmjdkdw">
|
||||
<div>
|
||||
<div>
|
||||
<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
||||
<MkSelect v-model="origin" style="margin: 0; flex: 1;">
|
||||
|
|
@ -109,9 +109,3 @@ definePageMetadata(computed(() => ({
|
|||
icon: 'ti ti-cloud',
|
||||
})));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xrmjdkdw {
|
||||
margin: var(--margin);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||
<FormSuspense :p="init">
|
||||
none
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="enableChartsForRemoteUser">
|
||||
<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="enableChartsForFederatedInstances">
|
||||
<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
|
|
@ -17,13 +25,22 @@ import * as os from '@/os';
|
|||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
||||
let enableChartsForRemoteUser: boolean = $ref(false);
|
||||
let enableChartsForFederatedInstances: boolean = $ref(false);
|
||||
|
||||
async function init() {
|
||||
await os.api('admin/meta');
|
||||
const meta = await os.api('admin/meta');
|
||||
enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
|
||||
enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta').then(() => {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableChartsForRemoteUser,
|
||||
enableChartsForFederatedInstances,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,3 @@ onMounted(() => {
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -132,7 +132,3 @@ defineExpose({
|
|||
pushData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@
|
|||
import { markRaw, onMounted, onUnmounted, ref } from 'vue';
|
||||
import XChart from './overview.queue.chart.vue';
|
||||
import number from '@/filters/number';
|
||||
import { stream } from '@/stream';
|
||||
import { useStream } from '@/stream';
|
||||
|
||||
const connection = markRaw(stream.useChannel('queueStats'));
|
||||
const connection = markRaw(useStream().useChannel('queueStats'));
|
||||
|
||||
const activeSincePrevTick = ref(0);
|
||||
const active = ref(0);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MkSpacer :content-max="1000">
|
||||
<div ref="rootEl" class="edbbcaef">
|
||||
<div ref="rootEl" :class="$style.root">
|
||||
<MkFoldableSection class="item">
|
||||
<template #header>Stats</template>
|
||||
<XStats/>
|
||||
|
|
@ -72,7 +72,7 @@ import XRetention from './overview.retention.vue';
|
|||
import XModerators from './overview.moderators.vue';
|
||||
import XHeatmap from './overview.heatmap.vue';
|
||||
import * as os from '@/os';
|
||||
import { stream } from '@/stream';
|
||||
import { useStream } from '@/stream';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
|
|
@ -87,7 +87,7 @@ let federationSubActive = $ref<number | null>(null);
|
|||
let federationSubActiveDiff = $ref<number | null>(null);
|
||||
let newUsers = $ref(null);
|
||||
let activeInstances = $shallowRef(null);
|
||||
const queueStatsConnection = markRaw(stream.useChannel('queueStats'));
|
||||
const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
|
||||
const now = new Date();
|
||||
const filesPagination = {
|
||||
endpoint: 'admin/drive/files' as const,
|
||||
|
|
@ -176,8 +176,8 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edbbcaef {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
grid-gap: 16px;
|
||||
|
|
|
|||
|
|
@ -132,7 +132,3 @@ defineExpose({
|
|||
pushData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ import { markRaw, onMounted, onUnmounted, ref } from 'vue';
|
|||
import XChart from './queue.chart.chart.vue';
|
||||
import number from '@/filters/number';
|
||||
import * as os from '@/os';
|
||||
import { stream } from '@/stream';
|
||||
import { useStream } from '@/stream';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const connection = markRaw(stream.useChannel('queueStats'));
|
||||
const connection = markRaw(useStream().useChannel('queueStats'));
|
||||
|
||||
const activeSincePrevTick = ref(0);
|
||||
const active = ref(0);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="name">
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<template #label>{{ i18n.ts.instanceDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<FormSplit :min-width="300">
|
||||
<FormSplit :minWidth="300">
|
||||
<MkInput v-model="maintainerName">
|
||||
<template #label>{{ i18n.ts.maintainerName }}</template>
|
||||
</MkInput>
|
||||
|
|
@ -29,18 +29,6 @@
|
|||
<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="enableChartsForRemoteUser">
|
||||
<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="enableChartsForFederatedInstances">
|
||||
<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.theme }}</template>
|
||||
|
||||
|
|
@ -128,7 +116,7 @@
|
|||
</MkSpacer>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
||||
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
|
|
@ -166,8 +154,6 @@ let defaultDarkTheme: any = $ref(null);
|
|||
let pinnedUsers: string = $ref('');
|
||||
let cacheRemoteFiles: boolean = $ref(false);
|
||||
let enableServiceWorker: boolean = $ref(false);
|
||||
let enableChartsForRemoteUser: boolean = $ref(false);
|
||||
let enableChartsForFederatedInstances: boolean = $ref(false);
|
||||
let swPublicKey: any = $ref(null);
|
||||
let swPrivateKey: any = $ref(null);
|
||||
let deeplAuthKey: string = $ref('');
|
||||
|
|
@ -188,8 +174,6 @@ async function init() {
|
|||
pinnedUsers = meta.pinnedUsers.join('\n');
|
||||
cacheRemoteFiles = meta.cacheRemoteFiles;
|
||||
enableServiceWorker = meta.enableServiceWorker;
|
||||
enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
|
||||
enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
|
||||
swPublicKey = meta.swPublickey;
|
||||
swPrivateKey = meta.swPrivateKey;
|
||||
deeplAuthKey = meta.deeplAuthKey;
|
||||
|
|
@ -211,8 +195,6 @@ function save() {
|
|||
pinnedUsers: pinnedUsers.split('\n'),
|
||||
cacheRemoteFiles,
|
||||
enableServiceWorker,
|
||||
enableChartsForRemoteUser,
|
||||
enableChartsForFederatedInstances,
|
||||
swPublicKey,
|
||||
swPrivateKey,
|
||||
deeplAuthKey,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
<div v-if="channel && tab === 'overview'" class="_gaps">
|
||||
<div class="_panel" :class="$style.bannerContainer">
|
||||
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
|
||||
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
||||
<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
|
||||
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" :class="$style.banner">
|
||||
<div :class="$style.bannerStatus">
|
||||
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
|
||||
|
|
@ -13,13 +15,10 @@
|
|||
<div :class="$style.bannerFade"></div>
|
||||
</div>
|
||||
<div v-if="channel.description" :class="$style.description">
|
||||
<Mfm :text="channel.description" :is-note="false" :i="$i"/>
|
||||
<Mfm :text="channel.description" :isNote="false" :i="$i"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
||||
<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-star"></i></MkButton>
|
||||
|
||||
<MkFoldableSection>
|
||||
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
|
||||
<div v-if="channel.pinnedNotes.length > 0" class="_gaps">
|
||||
|
|
@ -229,6 +228,13 @@ definePageMetadata(computed(() => channel ? {
|
|||
left: 16px;
|
||||
}
|
||||
|
||||
.favorite {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div v-if="clip">
|
||||
<div class="okzinsic _panel">
|
||||
<div v-if="clip.description" class="description">
|
||||
<Mfm :text="clip.description" :is-note="false" :i="$i"/>
|
||||
<Mfm :text="clip.description" :isNote="false" :i="$i"/>
|
||||
</div>
|
||||
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
|
||||
<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="900">
|
||||
<MkSpacer :contentMax="900">
|
||||
<div class="ogwlenmc">
|
||||
<div v-if="tab === 'local'" class="local">
|
||||
<MkInput v-model="query" :debounce="true" type="search">
|
||||
|
|
@ -123,15 +123,14 @@ const toggleSelect = (emoji) => {
|
|||
};
|
||||
|
||||
const add = async (ev: MouseEvent) => {
|
||||
const files = await selectFiles(ev.currentTarget ?? ev.target, null);
|
||||
|
||||
const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
|
||||
fileId: file.id,
|
||||
})));
|
||||
promise.then(() => {
|
||||
emojisPaginationComponent.value.reload();
|
||||
});
|
||||
os.promiseDialog(promise);
|
||||
os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), {
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.created) {
|
||||
emojisPaginationComponent.value.prepend(result.created);
|
||||
}
|
||||
},
|
||||
}, 'closed');
|
||||
};
|
||||
|
||||
const edit = (emoji) => {
|
||||
|
|
|
|||
|
|
@ -1,84 +1,171 @@
|
|||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="370"
|
||||
:with-ok-button="true"
|
||||
@close="$refs.dialog.close()"
|
||||
:width="400"
|
||||
@close="dialog.close()"
|
||||
@closed="$emit('closed')"
|
||||
@ok="ok()"
|
||||
>
|
||||
<template #header>:{{ emoji.name }}:</template>
|
||||
<template v-if="emoji" #header>:{{ emoji.name }}:</template>
|
||||
<template v-else #header>New emoji</template>
|
||||
|
||||
<MkSpacer :margin-min="20" :margin-max="28">
|
||||
<div class="yigymqpb _gaps_m">
|
||||
<img :src="`/emoji/${emoji.name}.webp`" class="img"/>
|
||||
<MkInput v-model="name">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||
<template #label>{{ i18n.ts.category }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="aliases">
|
||||
<template #label>{{ i18n.ts.tags }}</template>
|
||||
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="license">
|
||||
<template #label>{{ i18n.ts.license }}</template>
|
||||
</MkInput>
|
||||
<MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
<div>
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div class="_gaps_m">
|
||||
<div v-if="imgUrl != null" :class="$style.imgs">
|
||||
<div style="background: #000;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img"/>
|
||||
</div>
|
||||
<div style="background: #222;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img"/>
|
||||
</div>
|
||||
<div style="background: #ddd;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img"/>
|
||||
</div>
|
||||
<div style="background: #fff;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
|
||||
<MkInput v-model="name">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||
<template #label>{{ i18n.ts.category }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="aliases">
|
||||
<template #label>{{ i18n.ts.tags }}</template>
|
||||
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="license">
|
||||
<template #label>{{ i18n.ts.license }}</template>
|
||||
</MkInput>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
|
||||
<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
|
||||
<div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem">
|
||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
|
||||
<MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo>
|
||||
<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||
<MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<div :class="$style.footer">
|
||||
<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { customEmojiCategories } from '@/custom-emojis';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { selectFile, selectFiles } from '@/scripts/select-file';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
emoji: any,
|
||||
emoji?: any,
|
||||
}>();
|
||||
|
||||
let dialog = $ref(null);
|
||||
let name: string = $ref(props.emoji.name);
|
||||
let category: string = $ref(props.emoji.category);
|
||||
let aliases: string = $ref(props.emoji.aliases.join(' '));
|
||||
let license: string = $ref(props.emoji.license ?? '');
|
||||
let name: string = $ref(props.emoji ? props.emoji.name : '');
|
||||
let category: string = $ref(props.emoji ? props.emoji.category : '');
|
||||
let aliases: string = $ref(props.emoji ? props.emoji.aliases.join(' ') : '');
|
||||
let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : '');
|
||||
let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
|
||||
let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
|
||||
let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
||||
let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
|
||||
let file = $ref();
|
||||
|
||||
watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
|
||||
rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||
}, { immediate: true });
|
||||
|
||||
const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
|
||||
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
function ok() {
|
||||
update();
|
||||
async function changeImage(ev) {
|
||||
file = await selectFile(ev.currentTarget ?? ev.target, null);
|
||||
}
|
||||
|
||||
async function update() {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: props.emoji.id,
|
||||
async function addRole() {
|
||||
const roles = await os.api('admin/roles/list');
|
||||
const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id);
|
||||
|
||||
const { canceled, result: role } = await os.select({
|
||||
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
rolesThatCanBeUsedThisEmojiAsReaction.push(role);
|
||||
}
|
||||
|
||||
async function removeRole(role, ev) {
|
||||
rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
||||
}
|
||||
|
||||
async function done() {
|
||||
const params = {
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
category: category === '' ? null : category,
|
||||
aliases: aliases.split(' ').filter(x => x !== ''),
|
||||
license: license === '' ? null : license,
|
||||
});
|
||||
isSensitive,
|
||||
localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
|
||||
};
|
||||
|
||||
emit('done', {
|
||||
updated: {
|
||||
if (file) {
|
||||
params.fileId = file.id;
|
||||
}
|
||||
|
||||
if (props.emoji) {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: props.emoji.id,
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
license: license === '' ? null : license,
|
||||
},
|
||||
});
|
||||
...params,
|
||||
});
|
||||
|
||||
dialog.close();
|
||||
emit('done', {
|
||||
updated: {
|
||||
id: props.emoji.id,
|
||||
...params,
|
||||
},
|
||||
});
|
||||
|
||||
dialog.close();
|
||||
} else {
|
||||
const created = await os.apiWithDialog('admin/emoji/add', params);
|
||||
|
||||
emit('done', {
|
||||
created: created,
|
||||
});
|
||||
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function del() {
|
||||
|
|
@ -99,12 +186,48 @@ async function del() {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.yigymqpb {
|
||||
> .img {
|
||||
display: block;
|
||||
height: 64px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
<style lang="scss" module>
|
||||
.imgs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.imgContainer {
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.img {
|
||||
display: block;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.roleItem {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.role {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.roleUnassign {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-left: 8px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
|||
import MkInput from '@/components/MkInput.vue';
|
||||
import { useRouter } from '@/router';
|
||||
|
||||
const PRESET_DEFAULT = `/// @ 0.13.2
|
||||
const PRESET_DEFAULT = `/// @ 0.13.3
|
||||
|
||||
var name = ""
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ Ui:render([
|
|||
])
|
||||
`;
|
||||
|
||||
const PRESET_OMIKUJI = `/// @ 0.13.2
|
||||
const PRESET_OMIKUJI = `/// @ 0.13.3
|
||||
// ユーザーごとに日替わりのおみくじのプリセット
|
||||
|
||||
// 選択肢
|
||||
|
|
@ -94,7 +94,7 @@ Ui:render([
|
|||
])
|
||||
`;
|
||||
|
||||
const PRESET_SHUFFLE = `/// @ 0.13.2
|
||||
const PRESET_SHUFFLE = `/// @ 0.13.3
|
||||
// 巻き戻し可能な文字シャッフルのプリセット
|
||||
|
||||
let string = "ペペロンチーノ"
|
||||
|
|
@ -173,7 +173,7 @@ var cursor = 0
|
|||
do()
|
||||
`;
|
||||
|
||||
const PRESET_QUIZ = `/// @ 0.13.2
|
||||
const PRESET_QUIZ = `/// @ 0.13.3
|
||||
let title = '地理クイズ'
|
||||
|
||||
let qas = [{
|
||||
|
|
@ -286,7 +286,7 @@ qaEls.push(Ui:C:container({
|
|||
Ui:render(qaEls)
|
||||
`;
|
||||
|
||||
const PRESET_TIMELINE = `/// @ 0.13.2
|
||||
const PRESET_TIMELINE = `/// @ 0.13.3
|
||||
// APIリクエストを行いローカルタイムラインを表示するプリセット
|
||||
|
||||
@fetch() {
|
||||
|
|
@ -442,7 +442,3 @@ definePageMetadata(computed(() => flash ? {
|
|||
title: i18n.ts._play.new,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<MkFoldableSection class="_margin">
|
||||
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
||||
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disable-auto-load="true">
|
||||
<div class="vfpdbgtk">
|
||||
<div :class="$style.items">
|
||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<MkFoldableSection class="_margin">
|
||||
<template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template>
|
||||
<MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disable-auto-load="true">
|
||||
<div class="vfpdbgtk">
|
||||
<div :class="$style.items">
|
||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div v-else-if="tab === 'liked'">
|
||||
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
||||
<div class="vfpdbgtk">
|
||||
<div :class="$style.items">
|
||||
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
<div v-else-if="tab === 'my'">
|
||||
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
||||
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
||||
<div class="vfpdbgtk">
|
||||
<div :class="$style.items">
|
||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
|
|
@ -119,15 +119,11 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vfpdbgtk {
|
||||
<style lang="scss" module>
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
grid-gap: 12px;
|
||||
margin: 0 var(--margin);
|
||||
|
||||
> .post {
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
148
packages/frontend/src/pages/list.vue
Normal file
148
packages/frontend/src/pages/list.vue
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<p :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ i18n.ts.nothing }}
|
||||
</p>
|
||||
</div>
|
||||
</MKSpacer>
|
||||
<MkSpacer v-else-if="list" :content-max="700" :class="$style.main">
|
||||
<div v-if="list" class="members _margin">
|
||||
<div :class="$style.member_text">{{ i18n.ts.members }}</div>
|
||||
<div class="_gaps_s">
|
||||
<div v-for="user in users" :key="user.id" :class="$style.userItem">
|
||||
<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
|
||||
<MkUserCardMini :user="user"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" as-like primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
||||
<MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" as-like @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
||||
<MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, computed } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import { userPage } from '@/filters/user';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const props = defineProps<{
|
||||
listId: string;
|
||||
}>();
|
||||
|
||||
let list = $ref(null);
|
||||
let error = $ref();
|
||||
let users = $ref([]);
|
||||
|
||||
function fetchList(): void {
|
||||
os.api('users/lists/show', {
|
||||
listId: props.listId,
|
||||
forPublic: true,
|
||||
}).then(_list => {
|
||||
list = _list;
|
||||
os.api('users/show', {
|
||||
userIds: list.userIds,
|
||||
}).then(_users => {
|
||||
users = _users;
|
||||
});
|
||||
}).catch(err => {
|
||||
error = err;
|
||||
});
|
||||
}
|
||||
|
||||
function like() {
|
||||
os.apiWithDialog('users/lists/favorite', {
|
||||
listId: list.id,
|
||||
}).then(() => {
|
||||
list.isLiked = true;
|
||||
list.likedCount++;
|
||||
});
|
||||
}
|
||||
|
||||
function unlike() {
|
||||
os.apiWithDialog('users/lists/unfavorite', {
|
||||
listId: list.id,
|
||||
}).then(() => {
|
||||
list.isLiked = false;
|
||||
list.likedCount--;
|
||||
});
|
||||
}
|
||||
|
||||
async function create() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts.enterListName,
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.id });
|
||||
}
|
||||
|
||||
watch(() => props.listId, fetchList, { immediate: true });
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata(computed(() => list ? {
|
||||
title: list.name,
|
||||
icon: 'ti ti-list',
|
||||
} : null));
|
||||
</script>
|
||||
<style lang="scss" module>
|
||||
.main {
|
||||
min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
|
||||
}
|
||||
|
||||
.userItem {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.userItemBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.member_text {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.root {
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.img {
|
||||
vertical-align: bottom;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.import {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -38,7 +38,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-antenna',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-antenna',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MkSpacer :content-max="700">
|
||||
<div class="shaynizk">
|
||||
<div>
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="name">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
|
||||
<MkSwitch v-model="notify">{{ i18n.ts.notifyAntenna }}</MkSwitch>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div :class="$style.actions">
|
||||
<MkButton inline primary @click="saveAntenna()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton v-if="antenna.id != null" inline danger @click="deleteAntenna()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
|
|
@ -128,12 +128,10 @@ function addUser() {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shaynizk {
|
||||
> .actions {
|
||||
margin-top: 16px;
|
||||
padding: 24px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
<style lang="scss" module>
|
||||
.actions {
|
||||
margin-top: 16px;
|
||||
padding: 24px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ definePageMetadata({
|
|||
padding: 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
border: solid 1px var(--accent);
|
||||
|
|
|
|||
|
|
@ -1,35 +1,43 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700" :class="$style.main">
|
||||
<div v-if="list" class="members _margin">
|
||||
<div class="">{{ i18n.ts.members }}</div>
|
||||
<div class="_gaps_s">
|
||||
<div v-for="user in users" :key="user.id" :class="$style.userItem">
|
||||
<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
|
||||
<MkUserCardMini :user="user"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
|
||||
<MkSpacer :contentMax="700" :class="$style.main">
|
||||
<div v-if="list" class="_gaps">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.settings }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkInput v-model="name">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="isPublic">{{ i18n.ts.public }}</MkSwitch>
|
||||
<div class="_buttons">
|
||||
<MkButton rounded primary @click="updateSettings">{{ i18n.ts.save }}</MkButton>
|
||||
<MkButton rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder defaultOpen>
|
||||
<template #label>{{ i18n.ts.members }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||
<div v-for="user in users" :key="user.id" :class="$style.userItem">
|
||||
<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
|
||||
<MkUserCardMini :user="user"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
|
||||
<div class="_buttons">
|
||||
<MkButton inline rounded primary @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||
<MkButton inline rounded @click="renameList()">{{ i18n.ts.rename }}</MkButton>
|
||||
<MkButton inline rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
</template>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { mainRouter } from '@/router';
|
||||
|
|
@ -37,6 +45,9 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
|||
import { i18n } from '@/i18n';
|
||||
import { userPage } from '@/filters/user';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { userListsCache } from '@/cache';
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -45,12 +56,17 @@ const props = defineProps<{
|
|||
|
||||
let list = $ref(null);
|
||||
let users = $ref([]);
|
||||
const isPublic = ref(false);
|
||||
const name = ref('');
|
||||
|
||||
function fetchList() {
|
||||
os.api('users/lists/show', {
|
||||
listId: props.listId,
|
||||
}).then(_list => {
|
||||
list = _list;
|
||||
name.value = list.name;
|
||||
isPublic.value = list.isPublic;
|
||||
|
||||
os.api('users/show', {
|
||||
userIds: list.userIds,
|
||||
}).then(_users => {
|
||||
|
|
@ -86,23 +102,6 @@ async function removeUser(user, ev) {
|
|||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function renameList() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts.enterListName,
|
||||
default: list.name,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.api('users/lists/update', {
|
||||
listId: list.id,
|
||||
name: name,
|
||||
});
|
||||
|
||||
userListsCache.delete();
|
||||
|
||||
list.name = name;
|
||||
}
|
||||
|
||||
async function deleteList() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
|
|
@ -117,6 +116,19 @@ async function deleteList() {
|
|||
mainRouter.push('/my/lists');
|
||||
}
|
||||
|
||||
async function updateSettings() {
|
||||
await os.apiWithDialog('users/lists/update', {
|
||||
listId: list.id,
|
||||
name: name.value,
|
||||
isPublic: isPublic.value,
|
||||
});
|
||||
|
||||
userListsCache.delete();
|
||||
|
||||
list.name = name.value;
|
||||
list.isPublic = isPublic.value;
|
||||
}
|
||||
|
||||
watch(() => props.listId, fetchList, { immediate: true });
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
</button>
|
||||
</template>
|
||||
|
||||
<section class="oyyftmcf">
|
||||
<MkDriveFileThumbnail v-if="file" class="preview" :file="file" fit="contain" @click="choose()"/>
|
||||
<section>
|
||||
<MkDriveFileThumbnail v-if="file" style="height: 150px;" :file="file" fit="contain" @click="choose()"/>
|
||||
</section>
|
||||
</XContainer>
|
||||
</template>
|
||||
|
|
@ -54,11 +54,3 @@ onMounted(async () => {
|
|||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.oyyftmcf {
|
||||
> .preview {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
||||
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
||||
|
||||
<section class="vckmsadr">
|
||||
<textarea v-model="text"></textarea>
|
||||
<section>
|
||||
<textarea v-model="text" :class="$style.textarea"></textarea>
|
||||
</section>
|
||||
</XContainer>
|
||||
</template>
|
||||
|
|
@ -33,23 +33,21 @@ watch($$(text), () => {
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vckmsadr {
|
||||
> textarea {
|
||||
display: block;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
min-height: 150px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<style lang="scss" module>
|
||||
.textarea {
|
||||
display: block;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
min-height: 150px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,49 +9,41 @@
|
|||
</Sortable>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import XSection from './els/page-editor.el.section.vue';
|
||||
import XText from './els/page-editor.el.text.vue';
|
||||
import XImage from './els/page-editor.el.image.vue';
|
||||
import XNote from './els/page-editor.el.note.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Sortable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
|
||||
XSection, XText, XImage, XNote,
|
||||
},
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
const props = defineProps<{
|
||||
modelValue: any[];
|
||||
}>();
|
||||
|
||||
emits: ['update:modelValue'],
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', value: any[]): void;
|
||||
}>();
|
||||
|
||||
methods: {
|
||||
updateItem(v) {
|
||||
const i = this.modelValue.findIndex(x => x.id === v.id);
|
||||
const newValue = [
|
||||
...this.modelValue.slice(0, i),
|
||||
v,
|
||||
...this.modelValue.slice(i + 1),
|
||||
];
|
||||
this.$emit('update:modelValue', newValue);
|
||||
},
|
||||
function updateItem(v) {
|
||||
const i = props.modelValue.findIndex(x => x.id === v.id);
|
||||
const newValue = [
|
||||
...props.modelValue.slice(0, i),
|
||||
v,
|
||||
...props.modelValue.slice(i + 1),
|
||||
];
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
|
||||
removeItem(el) {
|
||||
const i = this.modelValue.findIndex(x => x.id === el.id);
|
||||
const newValue = [
|
||||
...this.modelValue.slice(0, i),
|
||||
...this.modelValue.slice(i + 1),
|
||||
];
|
||||
this.$emit('update:modelValue', newValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
function removeItem(el) {
|
||||
const i = props.modelValue.findIndex(x => x.id === el.id);
|
||||
const newValue = [
|
||||
...props.modelValue.slice(0, i),
|
||||
...props.modelValue.slice(i + 1),
|
||||
];
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
|
||||
<div class="cpjygsrt">
|
||||
<header>
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<div class="buttons">
|
||||
|
|
@ -16,58 +16,40 @@
|
|||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
|
||||
<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
|
||||
<div v-show="showBody" class="body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
removable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
error: {
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
warn: {
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['toggle', 'remove'],
|
||||
data() {
|
||||
return {
|
||||
showBody: this.expanded,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleContent(show: boolean) {
|
||||
this.showBody = show;
|
||||
this.$emit('toggle', show);
|
||||
},
|
||||
remove() {
|
||||
this.$emit('remove');
|
||||
},
|
||||
},
|
||||
const props = withDefaults(defineProps<{
|
||||
expanded?: boolean;
|
||||
removable?: boolean;
|
||||
draggable?: boolean;
|
||||
}>(), {
|
||||
expanded: true,
|
||||
removable: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'toggle', show: boolean): void;
|
||||
(ev: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const showBody = ref(props.expanded);
|
||||
|
||||
function toggleContent(show: boolean) {
|
||||
showBody.value = show;
|
||||
emit('toggle', show);
|
||||
}
|
||||
|
||||
function remove() {
|
||||
emit('remove');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -128,20 +110,6 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
> .warn {
|
||||
color: #b19e49;
|
||||
margin: 0;
|
||||
padding: 16px 16px 0 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> .error {
|
||||
color: #f00;
|
||||
margin: 0;
|
||||
padding: 16px 16px 0 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
|
||||
&:not(.inline):first-child {
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
<template>
|
||||
<div class="graojtoi">
|
||||
<MkSample/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import MkSample from '@/components/MkSample.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: i18n.ts.preview,
|
||||
icon: 'ti ti-eye',
|
||||
})));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.graojtoi {
|
||||
padding: var(--margin);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -93,6 +93,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-adjustments',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -118,6 +118,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-adjustments',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -68,6 +68,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-adjustments',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-lock',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
|
||||
<option value="force">{{ i18n.ts._nsfw.force }}</option>
|
||||
</MkSelect>
|
||||
<!--
|
||||
|
||||
<MkRadios v-model="mediaListWithOneImageAppearance">
|
||||
<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
|
||||
<option value="expand">{{ i18n.ts.default }}</option>
|
||||
|
|
@ -64,7 +64,6 @@
|
|||
<option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
|
||||
<option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
|
||||
</MkRadios>
|
||||
-->
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
|
|
@ -145,12 +144,14 @@
|
|||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch>
|
||||
<template #label>{{ i18n.ts.other }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
|
||||
<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
|
||||
<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
|
||||
|
||||
<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -212,10 +213,10 @@ const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'))
|
|||
const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
|
||||
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
|
||||
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
|
||||
const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
|
||||
const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
|
||||
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
|
||||
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
|
||||
const showTimelineReplies = computed(defaultStore.makeGetterSetter('showTimelineReplies'));
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
|
|
@ -244,7 +245,6 @@ watch([
|
|||
useSystemFont,
|
||||
enableInfiniteScroll,
|
||||
squareAvatars,
|
||||
aiChanMode,
|
||||
showNoteActionsOnlyHover,
|
||||
showGapBetweenNotesInTimeline,
|
||||
instanceTicker,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,17 @@
|
|||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
<template #label>{{ i18n.ts.developer }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="devMode">
|
||||
<template #label>{{ i18n.ts.devMode }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
|
|
@ -80,6 +91,7 @@ import FormSection from '@/components/form/section.vue';
|
|||
|
||||
const reportError = computed(defaultStore.makeGetterSetter('reportError'));
|
||||
const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
|
||||
const devMode = computed(defaultStore.makeGetterSetter('devMode'));
|
||||
|
||||
function onChangeInjectFeaturedNote(v) {
|
||||
os.api('i/update', {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-plug',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
|||
import * as os from '@/os';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { stream } from '@/stream';
|
||||
import { useStream } from '@/stream';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { version, host } from '@/config';
|
||||
|
|
@ -125,7 +125,7 @@ type Profile = {
|
|||
};
|
||||
};
|
||||
|
||||
const connection = $i && stream.useChannel('main');
|
||||
const connection = $i && useStream().useChannel('main');
|
||||
|
||||
let profiles = $ref<Record<string, Profile> | null>(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
<template>
|
||||
<div class="_gaps_m">
|
||||
<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div class="avatar">
|
||||
<MkAvatar class="avatar" :user="$i" @click="changeAvatar"/>
|
||||
<MkButton primary rounded class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div :class="$style.avatarContainer">
|
||||
<MkAvatar :class="$style.avatar" :user="$i" @click="changeAvatar"/>
|
||||
<MkButton primary rounded :class="$style.avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
</div>
|
||||
<MkButton primary rounded class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkInput v-model="profile.name" :max="30" manual-save>
|
||||
<MkInput v-model="profile.name" :max="30" manualSave>
|
||||
<template #label>{{ i18n.ts._profile.name }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model="profile.description" :max="500" tall manual-save>
|
||||
<MkTextarea v-model="profile.description" :max="500" tall manualSave>
|
||||
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkInput v-model="profile.location" manual-save>
|
||||
<MkInput v-model="profile.location" manualSave>
|
||||
<template #label>{{ i18n.ts.location }}</template>
|
||||
<template #prefix><i class="ti ti-map-pin"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="profile.birthday" type="date" manual-save>
|
||||
<MkInput v-model="profile.birthday" type="date" manualSave>
|
||||
<template #label>{{ i18n.ts.birthday }}</template>
|
||||
<template #prefix><i class="ti ti-cake"></i></template>
|
||||
</MkInput>
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
<Sortable
|
||||
v-model="fields"
|
||||
class="_gaps_s"
|
||||
item-key="id"
|
||||
itemKey="id"
|
||||
:animation="150"
|
||||
:handle="'.' + $style.dragItemHandle"
|
||||
@start="e => e.item.classList.add('active')"
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||
<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
|
||||
<div :class="$style.dragItemForm">
|
||||
<FormSplit :min-width="200">
|
||||
<FormSplit :minWidth="200">
|
||||
<MkInput v-model="element.name" small>
|
||||
<template #label>{{ i18n.ts._profile.metadataLabel }}</template>
|
||||
</MkInput>
|
||||
|
|
@ -88,11 +88,11 @@
|
|||
<MkSelect v-model="reactionAcceptance">
|
||||
<template #label>{{ i18n.ts.reactionAcceptance }}</template>
|
||||
<option :value="null">{{ i18n.ts.all }}</option>
|
||||
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
|
||||
<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
|
||||
<option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
|
||||
<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
|
||||
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -248,36 +248,39 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.llvierxe {
|
||||
<style lang="scss" module>
|
||||
.avatarAndBanner {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 10px;
|
||||
overflow: clip;
|
||||
|
||||
> .avatar {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
|
||||
> .avatar {
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: 0 auto 16px auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .bannerEdit {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" module>
|
||||
|
||||
.avatarContainer {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: 0 auto 16px auto;
|
||||
}
|
||||
|
||||
.avatarEdit {
|
||||
|
||||
}
|
||||
|
||||
.bannerEdit {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.metadataRoot {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
<FromSlot>
|
||||
<template #label>{{ i18n.ts.reactionSettingDescription }}</template>
|
||||
<div v-panel style="border-radius: 6px;">
|
||||
<Sortable v-model="reactions" class="zoaiodol" :item-key="item => item" :animation="150" :delay="100" :delay-on-touch-only="true">
|
||||
<Sortable v-model="reactions" :class="$style.reactions" :item-key="item => item" :animation="150" :delay="100" :delay-on-touch-only="true">
|
||||
<template #item="{element}">
|
||||
<button class="_button item" @click="remove(element, $event)">
|
||||
<button class="_button" :class="$style.reactionsItem" @click="remove(element, $event)">
|
||||
<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
|
||||
<MkEmoji v-else :emoji="element" :normal="true"/>
|
||||
</button>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button>
|
||||
<button class="_button" :class="$style.reactionsAdd" @click="chooseEmoji"><i class="ti ti-plus"></i></button>
|
||||
</template>
|
||||
</Sortable>
|
||||
</div>
|
||||
|
|
@ -135,20 +135,20 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zoaiodol {
|
||||
<style lang="scss" module>
|
||||
.reactions {
|
||||
padding: 12px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
cursor: move;
|
||||
}
|
||||
.reactionsItem {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
> .add {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
.reactionsAdd {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
class="_panel"
|
||||
@posted="state = 'posted'"
|
||||
/>
|
||||
<MkButton v-else-if="state === 'posted'" primary class="close" @click="close()">{{ i18n.ts.close }}</MkButton>
|
||||
<MkButton v-else-if="state === 'posted'" primary :class="$style.close" @click="close()">{{ i18n.ts.close }}</MkButton>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
|
@ -162,7 +162,7 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss" module>
|
||||
.close {
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,3 @@ definePageMetadata({
|
|||
icon: 'ti ti-user',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
</div>
|
||||
<div class="description">
|
||||
<MkOmit>
|
||||
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
|
||||
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" :i="$i"/>
|
||||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||
</MkOmit>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
||||
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
||||
<XClips v-else-if="tab === 'clips'" :user="user"/>
|
||||
<XLists v-else-if="tab === 'lists'" :user="user"/>
|
||||
<XPages v-else-if="tab === 'pages'" :user="user"/>
|
||||
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
|
||||
</div>
|
||||
|
|
@ -36,6 +37,7 @@ const XActivity = defineAsyncComponent(() => import('./activity.vue'));
|
|||
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
|
||||
const XReactions = defineAsyncComponent(() => import('./reactions.vue'));
|
||||
const XClips = defineAsyncComponent(() => import('./clips.vue'));
|
||||
const XLists = defineAsyncComponent(() => import('./lists.vue'));
|
||||
const XPages = defineAsyncComponent(() => import('./pages.vue'));
|
||||
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
|
||||
|
||||
|
|
@ -90,6 +92,10 @@ const headerTabs = $computed(() => user ? [{
|
|||
key: 'clips',
|
||||
title: i18n.ts.clips,
|
||||
icon: 'ti ti-paperclip',
|
||||
}, {
|
||||
key: 'lists',
|
||||
title: i18n.ts.lists,
|
||||
icon: 'ti ti-list',
|
||||
}, {
|
||||
key: 'pages',
|
||||
title: i18n.ts.pages,
|
||||
|
|
|
|||
51
packages/frontend/src/pages/user/lists.vue
Normal file
51
packages/frontend/src/pages/user/lists.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<MkSpacer :contentMax="700">
|
||||
<div>
|
||||
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists">
|
||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
|
||||
<div>{{ list.name }}</div>
|
||||
<MkAvatars :userIds="list.userIds"/>
|
||||
</MkA>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {} from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
||||
import MkSpacer from '@/components/global/MkSpacer.vue';
|
||||
import MkAvatars from '@/components/MkAvatars.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
user: misskey.entities.UserDetailed;
|
||||
}>();
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'users/lists/list' as const,
|
||||
noPaging: true,
|
||||
limit: 10,
|
||||
params: {
|
||||
userId: props.user.id,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.list {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
border: solid 1px var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,27 +1,32 @@
|
|||
<template>
|
||||
<form :class="$style.root" class="_panel" @submit.prevent="submit()">
|
||||
<div :class="$style.title">
|
||||
<div>Welcome to Misskey!</div>
|
||||
<div :class="$style.version">v{{ version }}</div>
|
||||
<div :class="$style.root">
|
||||
<MkAnimBg style="position: fixed; top: 0;"/>
|
||||
<div :class="$style.formContainer">
|
||||
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
|
||||
<div :class="$style.title">
|
||||
<div>Welcome to Misskey!</div>
|
||||
<div :class="$style.version">v{{ version }}</div>
|
||||
</div>
|
||||
<div class="_gaps_m" style="padding: 32px;">
|
||||
<div>{{ i18n.ts.intro }}</div>
|
||||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
|
||||
<template #label>{{ i18n.ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="password" type="password" data-cy-admin-password>
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
<template #prefix><i class="ti ti-lock"></i></template>
|
||||
</MkInput>
|
||||
<div>
|
||||
<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
|
||||
{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="_gaps_m" style="padding: 32px;">
|
||||
<div>{{ i18n.ts.intro }}</div>
|
||||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
|
||||
<template #label>{{ i18n.ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="password" type="password" data-cy-admin-password>
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
<template #prefix><i class="ti ti-lock"></i></template>
|
||||
</MkInput>
|
||||
<div>
|
||||
<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
|
||||
{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -32,6 +37,7 @@ import { host, version } from '@/config';
|
|||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkAnimBg from '@/components/MkAnimBg.vue';
|
||||
|
||||
let username = $ref('');
|
||||
let password = $ref('');
|
||||
|
|
@ -59,11 +65,23 @@ function submit() {
|
|||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
}
|
||||
|
||||
.formContainer {
|
||||
min-height: 100svh;
|
||||
padding: 32px 32px 64px 32px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
max-width: 500px;
|
||||
margin: 32px auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue