feat: server info widget

Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
ThatOneCalculator 2023-05-28 20:14:08 -07:00
parent 56359f0c2d
commit 544e3a008e
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
10 changed files with 181 additions and 50 deletions

View File

@ -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:

View File

@ -257,7 +257,9 @@ 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
@ -267,7 +269,10 @@ export const UserRepository = db.getRepository(User).extend({
})
: null;
return unread != null; } catch(e) { return false; }
return unread != null;
} catch (e) {
return false;
}
},
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -135,7 +135,9 @@ 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", {
@ -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,
},

View File

@ -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",

View 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>