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"
|
||||
saturday: "Saturday"
|
||||
_widgets:
|
||||
memo: "Sticky notes"
|
||||
memo: "Sticky Notes"
|
||||
notifications: "Notifications"
|
||||
timeline: "Timeline"
|
||||
calendar: "Calendar"
|
||||
trends: "Trending"
|
||||
clock: "Clock"
|
||||
rss: "RSS reader"
|
||||
rssTicker: "RSS-Ticker"
|
||||
rss: "RSS Reader"
|
||||
rssTicker: "RSS Ticker"
|
||||
activity: "Activity"
|
||||
photos: "Photos"
|
||||
digitalClock: "Digital clock"
|
||||
unixClock: "UNIX clock"
|
||||
digitalClock: "Digital Clock"
|
||||
unixClock: "UNIX Clock"
|
||||
federation: "Federation"
|
||||
instanceCloud: "Server cloud"
|
||||
postForm: "Posting form"
|
||||
instanceCloud: "Server Cloud"
|
||||
postForm: "Posting Form"
|
||||
slideshow: "Slideshow"
|
||||
button: "Button"
|
||||
onlineUsers: "Online users"
|
||||
onlineUsers: "Online Users"
|
||||
jobQueue: "Job Queue"
|
||||
serverMetric: "Server metrics"
|
||||
aiscript: "AiScript console"
|
||||
userList: "User list"
|
||||
serverMetric: "Server Metrics"
|
||||
aiscript: "AiScript Console"
|
||||
userList: "User List"
|
||||
serverInfo: "Server Info"
|
||||
_userList:
|
||||
chooseList: "Select a list"
|
||||
_cw:
|
||||
|
@ -257,17 +257,22 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
|
||||
async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
|
||||
try {
|
||||
const myAntennas = (await getAntennas()).filter((a) => a.userId === userId);
|
||||
const myAntennas = (await getAntennas()).filter(
|
||||
(a) => a.userId === userId,
|
||||
);
|
||||
|
||||
const unread =
|
||||
myAntennas.length > 0
|
||||
? await AntennaNotes.findOneBy({
|
||||
antennaId: In(myAntennas.map((x) => x.id)),
|
||||
read: false,
|
||||
})
|
||||
: null;
|
||||
const unread =
|
||||
myAntennas.length > 0
|
||||
? await AntennaNotes.findOneBy({
|
||||
antennaId: In(myAntennas.map((x) => x.id)),
|
||||
read: false,
|
||||
})
|
||||
: null;
|
||||
|
||||
return unread != null; } catch(e) { return false; }
|
||||
return unread != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
|
||||
|
@ -91,7 +91,7 @@ watch(
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
> .wrapper {
|
||||
display: table-cell;
|
||||
|
@ -11,10 +11,7 @@
|
||||
:data-count="previewableCount < 5 ? previewableCount : null"
|
||||
:class="{ dmWidth: inDm }"
|
||||
>
|
||||
<div
|
||||
ref="gallery"
|
||||
@click.stop
|
||||
>
|
||||
<div ref="gallery" @click.stop>
|
||||
<template
|
||||
v-for="media in mediaList.filter((media) =>
|
||||
previewable(media)
|
||||
@ -189,7 +186,9 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -251,14 +250,14 @@ const previewableCount = props.mediaList.filter((media) => previewable(media)).l
|
||||
display: grid;
|
||||
grid-gap: 8px;
|
||||
|
||||
> div, > button {
|
||||
> div,
|
||||
> button {
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
pointer-events: all;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
|
||||
> :nth-child(1) {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 2;
|
||||
|
@ -75,11 +75,11 @@ const hide = ref(
|
||||
onMounted(() => {
|
||||
mini.value = plyr.value.player.media.scrollWidth < 300;
|
||||
if (mini.value) {
|
||||
plyr.value.player.on('play', () => {
|
||||
plyr.value.player.on("play", () => {
|
||||
plyr.value.player.fullscreen.enter();
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -73,9 +73,7 @@
|
||||
:detailedView="true"
|
||||
:parentId="note.id"
|
||||
/>
|
||||
<MkLoading
|
||||
v-else-if="tab === 'replies' && note.repliesCount > 0"
|
||||
/>
|
||||
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
||||
|
||||
<MkNoteSub
|
||||
v-if="directQuotes && tab === 'quotes'"
|
||||
@ -103,9 +101,7 @@
|
||||
:with-chart="false"
|
||||
/>
|
||||
<!-- </MkPagination> -->
|
||||
<MkLoading
|
||||
v-else-if="tab === 'renotes' && note.renoteCount > 0"
|
||||
/>
|
||||
<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />
|
||||
|
||||
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
||||
<MkA
|
||||
|
@ -97,7 +97,10 @@
|
||||
:to="`/notes/${note.renoteId}`"
|
||||
>{{ 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" />
|
||||
<template v-if="detailed">
|
||||
<MkUrlPreview
|
||||
@ -151,7 +154,10 @@
|
||||
<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -188,13 +194,13 @@ const emit = defineEmits<{
|
||||
|
||||
const cwButton = ref<HTMLElement>();
|
||||
const showMoreButton = ref<HTMLElement>();
|
||||
const isLong = !props.detailedView
|
||||
&& ( props.note.cw == null
|
||||
&& (props.note.text != null
|
||||
&& (props.note.text.split("\n").length > 9 || props.note.text.length > 500)
|
||||
)
|
||||
|| props.note.files.length > 4
|
||||
);
|
||||
const isLong =
|
||||
!props.detailedView &&
|
||||
((props.note.cw == null &&
|
||||
props.note.text != null &&
|
||||
(props.note.text.split("\n").length > 9 ||
|
||||
props.note.text.length > 500)) ||
|
||||
props.note.files.length > 4);
|
||||
|
||||
const collapsed = $ref(props.note.cw == null && isLong);
|
||||
|
||||
@ -238,7 +244,8 @@ function focusFooter(ev) {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(a), :deep(button) {
|
||||
:deep(a),
|
||||
:deep(button) {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
@ -390,7 +397,7 @@ function focusFooter(ev) {
|
||||
background: var(--panel);
|
||||
mask: linear-gradient(to top, var(--gradient));
|
||||
-webkit-mask: linear-gradient(to top, var(--gradient));
|
||||
transition: background .2s;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +135,10 @@ function fetchNote() {
|
||||
note.text == null &&
|
||||
note.fileIds.length === 0 &&
|
||||
note.poll == null;
|
||||
appearNote = isRenote ? (note.renote as misskey.entities.Note) : note;
|
||||
|
||||
appearNote = isRenote
|
||||
? (note.renote as misskey.entities.Note)
|
||||
: note;
|
||||
|
||||
Promise.all([
|
||||
os.api("users/notes", {
|
||||
userId: note.userId,
|
||||
@ -178,7 +180,9 @@ definePageMetadata(
|
||||
path: `/notes/${appearNote.id}`,
|
||||
share: {
|
||||
title: i18n.t("noteOf", {
|
||||
user: appearNote.user.name || appearNote.user.username,
|
||||
user:
|
||||
appearNote.user.name ||
|
||||
appearNote.user.username,
|
||||
}),
|
||||
text: appearNote.text,
|
||||
},
|
||||
|
@ -89,6 +89,10 @@ export default function (app: App) {
|
||||
"MkwUserList",
|
||||
defineAsyncComponent(() => import("./user-list.vue")),
|
||||
);
|
||||
app.component(
|
||||
"MkwServerInfo",
|
||||
defineAsyncComponent(() => import("./server-info.vue")),
|
||||
);
|
||||
}
|
||||
|
||||
export const widgets = [
|
||||
@ -110,6 +114,7 @@ export const widgets = [
|
||||
"postForm",
|
||||
"slideshow",
|
||||
"serverMetric",
|
||||
"serverInfo",
|
||||
"onlineUsers",
|
||||
"jobQueue",
|
||||
"button",
|
||||
|
114
packages/client/src/widgets/server-info.vue
Normal file
114
packages/client/src/widgets/server-info.vue
Normal file
@ -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
Block a user