feat: ✨ server info widget
Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
parent
56359f0c2d
commit
544e3a008e
|
@ -1526,28 +1526,29 @@ _weekday:
|
||||||
friday: "Friday"
|
friday: "Friday"
|
||||||
saturday: "Saturday"
|
saturday: "Saturday"
|
||||||
_widgets:
|
_widgets:
|
||||||
memo: "Sticky notes"
|
memo: "Sticky Notes"
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
calendar: "Calendar"
|
calendar: "Calendar"
|
||||||
trends: "Trending"
|
trends: "Trending"
|
||||||
clock: "Clock"
|
clock: "Clock"
|
||||||
rss: "RSS reader"
|
rss: "RSS Reader"
|
||||||
rssTicker: "RSS-Ticker"
|
rssTicker: "RSS Ticker"
|
||||||
activity: "Activity"
|
activity: "Activity"
|
||||||
photos: "Photos"
|
photos: "Photos"
|
||||||
digitalClock: "Digital clock"
|
digitalClock: "Digital Clock"
|
||||||
unixClock: "UNIX clock"
|
unixClock: "UNIX Clock"
|
||||||
federation: "Federation"
|
federation: "Federation"
|
||||||
instanceCloud: "Server cloud"
|
instanceCloud: "Server Cloud"
|
||||||
postForm: "Posting form"
|
postForm: "Posting Form"
|
||||||
slideshow: "Slideshow"
|
slideshow: "Slideshow"
|
||||||
button: "Button"
|
button: "Button"
|
||||||
onlineUsers: "Online users"
|
onlineUsers: "Online Users"
|
||||||
jobQueue: "Job Queue"
|
jobQueue: "Job Queue"
|
||||||
serverMetric: "Server metrics"
|
serverMetric: "Server Metrics"
|
||||||
aiscript: "AiScript console"
|
aiscript: "AiScript Console"
|
||||||
userList: "User list"
|
userList: "User List"
|
||||||
|
serverInfo: "Server Info"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Select a list"
|
chooseList: "Select a list"
|
||||||
_cw:
|
_cw:
|
||||||
|
|
|
@ -257,7 +257,9 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
|
|
||||||
async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
|
async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const myAntennas = (await getAntennas()).filter((a) => a.userId === userId);
|
const myAntennas = (await getAntennas()).filter(
|
||||||
|
(a) => a.userId === userId,
|
||||||
|
);
|
||||||
|
|
||||||
const unread =
|
const unread =
|
||||||
myAntennas.length > 0
|
myAntennas.length > 0
|
||||||
|
@ -267,7 +269,10 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return unread != null; } catch(e) { return false; }
|
return unread != null;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
|
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
|
||||||
|
|
|
@ -91,7 +91,7 @@ watch(
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: rgba(0,0,0,0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
> .wrapper {
|
> .wrapper {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
:data-count="previewableCount < 5 ? previewableCount : null"
|
:data-count="previewableCount < 5 ? previewableCount : null"
|
||||||
:class="{ dmWidth: inDm }"
|
:class="{ dmWidth: inDm }"
|
||||||
>
|
>
|
||||||
<div
|
<div ref="gallery" @click.stop>
|
||||||
ref="gallery"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<template
|
<template
|
||||||
v-for="media in mediaList.filter((media) =>
|
v-for="media in mediaList.filter((media) =>
|
||||||
previewable(media)
|
previewable(media)
|
||||||
|
@ -189,7 +186,9 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
||||||
FILE_TYPE_BROWSERSAFE.includes(file.type)
|
FILE_TYPE_BROWSERSAFE.includes(file.type)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const previewableCount = props.mediaList.filter((media) => previewable(media)).length;
|
const previewableCount = props.mediaList.filter((media) =>
|
||||||
|
previewable(media)
|
||||||
|
).length;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -251,14 +250,14 @@ const previewableCount = props.mediaList.filter((media) => previewable(media)).l
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 8px;
|
grid-gap: 8px;
|
||||||
|
|
||||||
> div, > button {
|
> div,
|
||||||
|
> button {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
> :nth-child(1) {
|
> :nth-child(1) {
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 1 / 2;
|
grid-row: 1 / 2;
|
||||||
|
|
|
@ -75,11 +75,11 @@ const hide = ref(
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mini.value = plyr.value.player.media.scrollWidth < 300;
|
mini.value = plyr.value.player.media.scrollWidth < 300;
|
||||||
if (mini.value) {
|
if (mini.value) {
|
||||||
plyr.value.player.on('play', () => {
|
plyr.value.player.on("play", () => {
|
||||||
plyr.value.player.fullscreen.enter();
|
plyr.value.player.fullscreen.enter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -73,9 +73,7 @@
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
:parentId="note.id"
|
:parentId="note.id"
|
||||||
/>
|
/>
|
||||||
<MkLoading
|
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
||||||
v-else-if="tab === 'replies' && note.repliesCount > 0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="directQuotes && tab === 'quotes'"
|
v-if="directQuotes && tab === 'quotes'"
|
||||||
|
@ -103,9 +101,7 @@
|
||||||
:with-chart="false"
|
:with-chart="false"
|
||||||
/>
|
/>
|
||||||
<!-- </MkPagination> -->
|
<!-- </MkPagination> -->
|
||||||
<MkLoading
|
<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />
|
||||||
v-else-if="tab === 'renotes' && note.renoteCount > 0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
||||||
<MkA
|
<MkA
|
||||||
|
|
|
@ -97,7 +97,10 @@
|
||||||
:to="`/notes/${note.renoteId}`"
|
:to="`/notes/${note.renoteId}`"
|
||||||
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
||||||
>
|
>
|
||||||
<XMediaList v-if="note.files.length > 0" :media-list="note.files" />
|
<XMediaList
|
||||||
|
v-if="note.files.length > 0"
|
||||||
|
:media-list="note.files"
|
||||||
|
/>
|
||||||
<XPoll v-if="note.poll" :note="note" class="poll" />
|
<XPoll v-if="note.poll" :note="note" class="poll" />
|
||||||
<template v-if="detailed">
|
<template v-if="detailed">
|
||||||
<MkUrlPreview
|
<MkUrlPreview
|
||||||
|
@ -151,7 +154,10 @@
|
||||||
<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
|
<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
|
||||||
</template>
|
</template>
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<div v-if="(isLong && !collapsed) || (props.note.cw && showContent)" class="fade"></div>
|
<div
|
||||||
|
v-if="(isLong && !collapsed) || (props.note.cw && showContent)"
|
||||||
|
class="fade"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -188,13 +194,13 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const cwButton = ref<HTMLElement>();
|
const cwButton = ref<HTMLElement>();
|
||||||
const showMoreButton = ref<HTMLElement>();
|
const showMoreButton = ref<HTMLElement>();
|
||||||
const isLong = !props.detailedView
|
const isLong =
|
||||||
&& ( props.note.cw == null
|
!props.detailedView &&
|
||||||
&& (props.note.text != null
|
((props.note.cw == null &&
|
||||||
&& (props.note.text.split("\n").length > 9 || props.note.text.length > 500)
|
props.note.text != null &&
|
||||||
)
|
(props.note.text.split("\n").length > 9 ||
|
||||||
|| props.note.files.length > 4
|
props.note.text.length > 500)) ||
|
||||||
);
|
props.note.files.length > 4);
|
||||||
|
|
||||||
const collapsed = $ref(props.note.cw == null && isLong);
|
const collapsed = $ref(props.note.cw == null && isLong);
|
||||||
|
|
||||||
|
@ -238,7 +244,8 @@ function focusFooter(ev) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(a), :deep(button) {
|
:deep(a),
|
||||||
|
:deep(button) {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
@ -390,7 +397,7 @@ function focusFooter(ev) {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
mask: linear-gradient(to top, var(--gradient));
|
mask: linear-gradient(to top, var(--gradient));
|
||||||
-webkit-mask: linear-gradient(to top, var(--gradient));
|
-webkit-mask: linear-gradient(to top, var(--gradient));
|
||||||
transition: background .2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,9 @@ function fetchNote() {
|
||||||
note.text == null &&
|
note.text == null &&
|
||||||
note.fileIds.length === 0 &&
|
note.fileIds.length === 0 &&
|
||||||
note.poll == null;
|
note.poll == null;
|
||||||
appearNote = isRenote ? (note.renote as misskey.entities.Note) : note;
|
appearNote = isRenote
|
||||||
|
? (note.renote as misskey.entities.Note)
|
||||||
|
: note;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
os.api("users/notes", {
|
os.api("users/notes", {
|
||||||
|
@ -178,7 +180,9 @@ definePageMetadata(
|
||||||
path: `/notes/${appearNote.id}`,
|
path: `/notes/${appearNote.id}`,
|
||||||
share: {
|
share: {
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user: appearNote.user.name || appearNote.user.username,
|
user:
|
||||||
|
appearNote.user.name ||
|
||||||
|
appearNote.user.username,
|
||||||
}),
|
}),
|
||||||
text: appearNote.text,
|
text: appearNote.text,
|
||||||
},
|
},
|
||||||
|
|
|
@ -89,6 +89,10 @@ export default function (app: App) {
|
||||||
"MkwUserList",
|
"MkwUserList",
|
||||||
defineAsyncComponent(() => import("./user-list.vue")),
|
defineAsyncComponent(() => import("./user-list.vue")),
|
||||||
);
|
);
|
||||||
|
app.component(
|
||||||
|
"MkwServerInfo",
|
||||||
|
defineAsyncComponent(() => import("./server-info.vue")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const widgets = [
|
export const widgets = [
|
||||||
|
@ -110,6 +114,7 @@ export const widgets = [
|
||||||
"postForm",
|
"postForm",
|
||||||
"slideshow",
|
"slideshow",
|
||||||
"serverMetric",
|
"serverMetric",
|
||||||
|
"serverInfo",
|
||||||
"onlineUsers",
|
"onlineUsers",
|
||||||
"jobQueue",
|
"jobQueue",
|
||||||
"button",
|
"button",
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
<template>
|
||||||
|
<div class="_panel">
|
||||||
|
<div
|
||||||
|
:class="$style.container"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: instance.bannerUrl
|
||||||
|
? `url(${instance.bannerUrl})`
|
||||||
|
: null,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div :class="$style.iconContainer">
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
instance.iconUrl ??
|
||||||
|
instance.faviconUrl ??
|
||||||
|
'/favicon.ico'
|
||||||
|
"
|
||||||
|
alt=""
|
||||||
|
:class="$style.icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.bodyContainer">
|
||||||
|
<div :class="$style.body">
|
||||||
|
<MkA :class="$style.name" to="/about" behavior="window">{{
|
||||||
|
instance.name
|
||||||
|
}}</MkA>
|
||||||
|
<div :class="$style.host">{{ host }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
useWidgetPropsManager,
|
||||||
|
Widget,
|
||||||
|
WidgetComponentEmits,
|
||||||
|
WidgetComponentExpose,
|
||||||
|
WidgetComponentProps,
|
||||||
|
} from "./widget";
|
||||||
|
import { GetFormResultType } from "@/scripts/form";
|
||||||
|
import { host } from "@/config";
|
||||||
|
import { instance } from "@/instance";
|
||||||
|
|
||||||
|
const name = "serverInfo";
|
||||||
|
|
||||||
|
const widgetPropsDef = {};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(
|
||||||
|
name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconContainer {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: solid 3px var(--panelBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name,
|
||||||
|
.host {
|
||||||
|
color: var(--fg);
|
||||||
|
text-shadow: -1px -1px 0 var(--bg), 1px -1px 0 var(--bg),
|
||||||
|
-1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.host {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue