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

View file

@ -257,17 +257,22 @@ 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
? await AntennaNotes.findOneBy({ ? await AntennaNotes.findOneBy({
antennaId: In(myAntennas.map((x) => x.id)), antennaId: In(myAntennas.map((x) => x.id)),
read: false, read: false,
}) })
: 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> {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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>