Compare commits
6 Commits
b2543b29d3
...
80a29f771f
Author | SHA1 | Date |
---|---|---|
Natty | 80a29f771f | |
Natty | 2fea3b79a7 | |
Natty | 22208d4c68 | |
Natty | b2a55f1d6d | |
Natty | 1366adcebf | |
Natty | f06c74c85c |
|
@ -23,7 +23,6 @@ import {
|
||||||
NoteReaction,
|
NoteReaction,
|
||||||
Notification,
|
Notification,
|
||||||
OriginType,
|
OriginType,
|
||||||
Page,
|
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
Signin,
|
Signin,
|
||||||
Stats,
|
Stats,
|
||||||
|
@ -631,8 +630,6 @@ export type Endpoints = {
|
||||||
};
|
};
|
||||||
res: Notification[];
|
res: Notification[];
|
||||||
};
|
};
|
||||||
"i/page-likes": { req: TODO; res: TODO };
|
|
||||||
"i/pages": { req: TODO; res: TODO };
|
|
||||||
"i/pin": { req: { noteId: Note["id"] }; res: MeDetailed };
|
"i/pin": { req: { noteId: Note["id"] }; res: MeDetailed };
|
||||||
"i/read-all-messaging-messages": { req: TODO; res: TODO };
|
"i/read-all-messaging-messages": { req: TODO; res: TODO };
|
||||||
"i/read-all-unread-notes": { req: TODO; res: TODO };
|
"i/read-all-unread-notes": { req: TODO; res: TODO };
|
||||||
|
@ -886,24 +883,6 @@ export type Endpoints = {
|
||||||
res: null;
|
res: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// page-push
|
|
||||||
"page-push": {
|
|
||||||
req: { pageId: Page["id"]; event: string; var?: any };
|
|
||||||
res: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// pages
|
|
||||||
"pages/create": { req: TODO; res: Page };
|
|
||||||
"pages/delete": { req: { pageId: Page["id"] }; res: null };
|
|
||||||
"pages/featured": { req: NoParams; res: Page[] };
|
|
||||||
"pages/like": { req: { pageId: Page["id"] }; res: null };
|
|
||||||
"pages/show": {
|
|
||||||
req: { pageId?: Page["id"]; name?: string; username?: string };
|
|
||||||
res: Page;
|
|
||||||
};
|
|
||||||
"pages/unlike": { req: { pageId: Page["id"] }; res: null };
|
|
||||||
"pages/update": { req: TODO; res: null };
|
|
||||||
|
|
||||||
// ping
|
// ping
|
||||||
ping: { req: NoParams; res: { pong: number } };
|
ping: { req: NoParams; res: { pong: number } };
|
||||||
|
|
||||||
|
@ -1000,7 +979,6 @@ export type Endpoints = {
|
||||||
};
|
};
|
||||||
res: Note[];
|
res: Note[];
|
||||||
};
|
};
|
||||||
"users/pages": { req: TODO; res: TODO };
|
|
||||||
"users/recommendation": { req: TODO; res: TODO };
|
"users/recommendation": { req: TODO; res: TODO };
|
||||||
"users/relation": { req: TODO; res: TODO };
|
"users/relation": { req: TODO; res: TODO };
|
||||||
"users/report-abuse": { req: TODO; res: TODO };
|
"users/report-abuse": { req: TODO; res: TODO };
|
||||||
|
|
|
@ -64,8 +64,6 @@ export type UserDetailed = UserLite & {
|
||||||
notesCount: number;
|
notesCount: number;
|
||||||
pinnedNoteIds: ID[];
|
pinnedNoteIds: ID[];
|
||||||
pinnedNotes: Note[];
|
pinnedNotes: Note[];
|
||||||
pinnedPage: Page | null;
|
|
||||||
pinnedPageId: string | null;
|
|
||||||
publicReactions: boolean;
|
publicReactions: boolean;
|
||||||
securityKeys: boolean;
|
securityKeys: boolean;
|
||||||
twoFactorEnabled: boolean;
|
twoFactorEnabled: boolean;
|
||||||
|
@ -308,36 +306,6 @@ export type Stats = {
|
||||||
driveUsageRemote: number;
|
driveUsageRemote: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Page = {
|
|
||||||
id: ID;
|
|
||||||
createdAt: DateString;
|
|
||||||
updatedAt: DateString;
|
|
||||||
userId: User["id"];
|
|
||||||
user: User;
|
|
||||||
content: Record<string, any>[];
|
|
||||||
variables: Record<string, any>[];
|
|
||||||
title: string;
|
|
||||||
name: string;
|
|
||||||
summary: string | null;
|
|
||||||
hideTitleWhenPinned: boolean;
|
|
||||||
alignCenter: boolean;
|
|
||||||
font: string;
|
|
||||||
script: string;
|
|
||||||
eyeCatchingImageId: DriveFile["id"] | null;
|
|
||||||
eyeCatchingImage: DriveFile | null;
|
|
||||||
attachedFiles: any;
|
|
||||||
likedCount: number;
|
|
||||||
isLiked?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageEvent = {
|
|
||||||
pageId: Page["id"];
|
|
||||||
event: string;
|
|
||||||
var: any;
|
|
||||||
userId: User["id"];
|
|
||||||
user: User;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Announcement = {
|
export type Announcement = {
|
||||||
id: ID;
|
id: ID;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,6 @@
|
||||||
"@rollup/plugin-alias": "3.1.9",
|
"@rollup/plugin-alias": "3.1.9",
|
||||||
"@rollup/plugin-json": "4.1.0",
|
"@rollup/plugin-json": "4.1.0",
|
||||||
"@rollup/pluginutils": "^4.2.1",
|
"@rollup/pluginutils": "^4.2.1",
|
||||||
"@syuilo/aiscript": "0.11.1",
|
|
||||||
"@types/escape-regexp": "0.0.1",
|
"@types/escape-regexp": "0.0.1",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/gulp": "4.0.11",
|
"@types/gulp": "4.0.11",
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
"vue-isyourpasswordsafe": "^2.0.0",
|
"vue-isyourpasswordsafe": "^2.0.0",
|
||||||
"vue-plyr": "^7.0.0",
|
"vue-plyr": "^7.0.0",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
|
||||||
"vue3-otp-input": "^0.4.1",
|
"vue3-otp-input": "^0.4.1",
|
||||||
"vuedraggable": "4.1.0"
|
"vuedraggable": "4.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,7 +218,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
import { computed, inject, ref, toRaw } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import type * as misskey from "calckey-js";
|
import type * as misskey from "calckey-js";
|
||||||
import XNoteSub from "@/components/MagNoteSub.vue";
|
import XNoteSub from "@/components/MagNoteSub.vue";
|
||||||
|
@ -238,7 +238,7 @@ import { getWordSoftMute } from "@/scripts/check-word-mute";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -276,17 +276,6 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
return i18n.ts.userSaysSomething;
|
return i18n.ts.userSaysSomething;
|
||||||
};
|
};
|
||||||
|
|
||||||
// plugin
|
|
||||||
if (noteViewInterruptors.length > 0) {
|
|
||||||
onMounted(async () => {
|
|
||||||
let result = structuredClone(toRaw(note));
|
|
||||||
for (const interruptor of noteViewInterruptors) {
|
|
||||||
result = await interruptor.handler(result);
|
|
||||||
}
|
|
||||||
note = result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = ref<HTMLElement | null>(null);
|
const el = ref<HTMLElement | null>(null);
|
||||||
const footerEl = ref<HTMLElement | null>(null);
|
const footerEl = ref<HTMLElement | null>(null);
|
||||||
const menuButton = ref<HTMLElement | null>(null);
|
const menuButton = ref<HTMLElement | null>(null);
|
||||||
|
|
|
@ -164,7 +164,7 @@ import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -200,17 +200,6 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
return i18n.ts.userSaysSomething;
|
return i18n.ts.userSaysSomething;
|
||||||
};
|
};
|
||||||
|
|
||||||
// plugin
|
|
||||||
if (noteViewInterruptors.length > 0) {
|
|
||||||
onMounted(async () => {
|
|
||||||
let result = structuredClone(toRaw(note));
|
|
||||||
for (const interruptor of noteViewInterruptors) {
|
|
||||||
result = await interruptor.handler(result);
|
|
||||||
}
|
|
||||||
note = result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = ref<HTMLElement | null>(null);
|
const el = ref<HTMLElement | null>(null);
|
||||||
const noteEl = ref<HTMLElement | null>(null);
|
const noteEl = ref<HTMLElement | null>(null);
|
||||||
const menuButton = ref<HTMLElement | null>(null);
|
const menuButton = ref<HTMLElement | null>(null);
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
<template v-for="item in items">
|
<template v-for="item in items">
|
||||||
<button
|
<button
|
||||||
v-if="item.action"
|
v-if="item.action"
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="
|
@click="
|
||||||
($event) => {
|
($event) => {
|
||||||
|
@ -33,12 +32,7 @@
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<MkA
|
<MkA v-else :to="item.to" @click.passive="close()">
|
||||||
v-else
|
|
||||||
v-click-anime
|
|
||||||
:to="item.to"
|
|
||||||
@click.passive="close()"
|
|
||||||
>
|
|
||||||
<i class="icon" :class="item.icon"></i>
|
<i class="icon" :class="item.icon"></i>
|
||||||
<div class="text">{{ item.text }}</div>
|
<div class="text">{{ item.text }}</div>
|
||||||
<span v-if="item.indicate" class="indicator"
|
<span v-if="item.indicate" class="indicator"
|
||||||
|
@ -52,14 +46,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import { navbarItemDef } from "@/navbar";
|
import { navbarItemDef } from "@/navbar";
|
||||||
import { instanceName } from "@/config";
|
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -1,171 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkA
|
|
||||||
:to="`/@${page.user.username}/pages/${page.name}`"
|
|
||||||
class="vhpxefrj _block"
|
|
||||||
tabindex="-1"
|
|
||||||
:behavior="`${ui === 'deck' ? 'window' : null}`"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="page.eyeCatchingImage"
|
|
||||||
class="thumbnail"
|
|
||||||
:style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"
|
|
||||||
></div>
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h1 :title="page.title">{{ page.title }}</h1>
|
|
||||||
</header>
|
|
||||||
<p v-if="page.summary" :title="page.summary">
|
|
||||||
{{
|
|
||||||
page.summary.length > 85
|
|
||||||
? page.summary.slice(0, 85) + "…"
|
|
||||||
: page.summary
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<footer>
|
|
||||||
<img
|
|
||||||
class="icon"
|
|
||||||
:src="page.user.avatarUrl"
|
|
||||||
aria-label="none"
|
|
||||||
/>
|
|
||||||
<p>{{ userName(page.user) }}</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { userName } from "@/filters/user";
|
|
||||||
import { ui } from "@/config";
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
page: any;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vhpxefrj {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .thumbnail {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
font-size: 3.5em;
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
font-size: 4em;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& + article {
|
|
||||||
left: 100px;
|
|
||||||
width: calc(100% - 100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> article {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
> h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
color: var(--urlPreviewTitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--urlPreviewText);
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
> footer {
|
|
||||||
margin-top: 8px;
|
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
> p {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--urlPreviewInfo);
|
|
||||||
font-size: 0.8em;
|
|
||||||
line-height: 16px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
> .thumbnail {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
|
|
||||||
& + article {
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 550px) {
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
> .thumbnail {
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> article {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
font-size: 10px;
|
|
||||||
|
|
||||||
> .thumbnail {
|
|
||||||
height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> article {
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> footer {
|
|
||||||
margin-top: 4px;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -15,7 +15,6 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="$props.editId == null"
|
v-if="$props.editId == null"
|
||||||
v-click-anime
|
|
||||||
v-tooltip="i18n.ts.switchAccount"
|
v-tooltip="i18n.ts.switchAccount"
|
||||||
class="account _button"
|
class="account _button"
|
||||||
@click="openAccountMenu"
|
@click="openAccountMenu"
|
||||||
|
@ -198,14 +197,6 @@
|
||||||
>
|
>
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-if="postFormActions.length > 0"
|
|
||||||
v-tooltip="i18n.ts.plugin"
|
|
||||||
class="_button"
|
|
||||||
@click="showActions"
|
|
||||||
>
|
|
||||||
<i class="ph-plug ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<!-- v-if="showMfmCheatsheet" -->
|
<!-- v-if="showMfmCheatsheet" -->
|
||||||
<button
|
<button
|
||||||
v-tooltip="i18n.ts._mfm.cheatSheet"
|
v-tooltip="i18n.ts._mfm.cheatSheet"
|
||||||
|
@ -245,7 +236,7 @@ import { formatTimeString } from "@/scripts/format-time-string";
|
||||||
import { Autocomplete } from "@/scripts/autocomplete";
|
import { Autocomplete } from "@/scripts/autocomplete";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { selectFiles } from "@/scripts/select-file";
|
import { selectFiles } from "@/scripts/select-file";
|
||||||
import { defaultStore, notePostInterruptors, postFormActions } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import MkInfo from "@/components/MkInfo.vue";
|
import MkInfo from "@/components/MkInfo.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
|
@ -847,13 +838,6 @@ async function post() {
|
||||||
: hashtags_;
|
: hashtags_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// plugin
|
|
||||||
if (notePostInterruptors.length > 0) {
|
|
||||||
for (const interruptor of notePostInterruptors) {
|
|
||||||
postData = await interruptor.handler(structuredClone(postData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = undefined;
|
let token = undefined;
|
||||||
|
|
||||||
if (postAccount) {
|
if (postAccount) {
|
||||||
|
@ -912,27 +896,6 @@ async function openCheatSheet(ev: MouseEvent) {
|
||||||
os.popup(XCheatSheet, {}, {}, "closed");
|
os.popup(XCheatSheet, {}, {}, "closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showActions(ev) {
|
|
||||||
os.popupMenu(
|
|
||||||
postFormActions.map((action) => ({
|
|
||||||
text: action.title,
|
|
||||||
action: () => {
|
|
||||||
action.handler(
|
|
||||||
{
|
|
||||||
text: text,
|
|
||||||
},
|
|
||||||
(key, value) => {
|
|
||||||
if (key === "text") {
|
|
||||||
text = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
ev.currentTarget ?? ev.target
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let postAccount = $ref<misskey.entities.UserDetailed | null>(null);
|
let postAccount = $ref<misskey.entities.UserDetailed | null>(null);
|
||||||
|
|
||||||
function openAccountMenu(ev: MouseEvent) {
|
function openAccountMenu(ev: MouseEvent) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, h, resolveDirective, withDirectives } from "vue";
|
import { defineComponent, h } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -24,27 +24,24 @@ export default defineComponent({
|
||||||
role: "tablist",
|
role: "tablist",
|
||||||
},
|
},
|
||||||
options.map((option) =>
|
options.map((option) =>
|
||||||
withDirectives(
|
h(
|
||||||
h(
|
"button",
|
||||||
"button",
|
{
|
||||||
{
|
class: "_button",
|
||||||
class: "_button",
|
role: "tab",
|
||||||
role: "tab",
|
key: option.key,
|
||||||
key: option.key,
|
"aria-selected":
|
||||||
"aria-selected":
|
this.modelValue === option.props?.value
|
||||||
this.modelValue === option.props?.value
|
? "true"
|
||||||
? "true"
|
: "false",
|
||||||
: "false",
|
onClick: () => {
|
||||||
onClick: () => {
|
this.$emit(
|
||||||
this.$emit(
|
"update:modelValue",
|
||||||
"update:modelValue",
|
option.props?.value
|
||||||
option.props?.value
|
);
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
option.children
|
},
|
||||||
),
|
option.children
|
||||||
[[resolveDirective("click-anime")]]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
<template>
|
|
||||||
<div ref="rootEl" class="meijqfqm">
|
|
||||||
<canvas
|
|
||||||
:id="idForCanvas"
|
|
||||||
ref="canvasEl"
|
|
||||||
class="canvas"
|
|
||||||
:width="width"
|
|
||||||
height="300"
|
|
||||||
@contextmenu.prevent="() => {}"
|
|
||||||
></canvas>
|
|
||||||
<div :id="idForTags" ref="tagsEl" class="tags">
|
|
||||||
<ul>
|
|
||||||
<slot></slot>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref, watch, PropType, onBeforeUnmount } from "vue";
|
|
||||||
import tinycolor from "tinycolor2";
|
|
||||||
|
|
||||||
const loaded = !!window.TagCanvas;
|
|
||||||
const SAFE_FOR_HTML_ID = "abcdefghijklmnopqrstuvwxyz";
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
|
||||||
const idForCanvas = Array.from(Array(16))
|
|
||||||
.map(
|
|
||||||
() =>
|
|
||||||
SAFE_FOR_HTML_ID[
|
|
||||||
Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
const idForTags = Array.from(Array(16))
|
|
||||||
.map(
|
|
||||||
() =>
|
|
||||||
SAFE_FOR_HTML_ID[
|
|
||||||
Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
let available = $ref(false);
|
|
||||||
let rootEl = $ref<HTMLElement | null>(null);
|
|
||||||
let canvasEl = $ref<HTMLCanvasElement | null>(null);
|
|
||||||
let tagsEl = $ref<HTMLElement | null>(null);
|
|
||||||
let width = $ref(300);
|
|
||||||
|
|
||||||
watch($$(available), () => {
|
|
||||||
try {
|
|
||||||
window.TagCanvas.Start(idForCanvas, idForTags, {
|
|
||||||
textColour: "#ffffff",
|
|
||||||
outlineColour: tinycolor(
|
|
||||||
computedStyle.getPropertyValue("--accent")
|
|
||||||
).toHexString(),
|
|
||||||
outlineRadius: 10,
|
|
||||||
initial: [-0.03, -0.01],
|
|
||||||
frontSelect: true,
|
|
||||||
imageRadius: 8,
|
|
||||||
//dragControl: true,
|
|
||||||
dragThreshold: 3,
|
|
||||||
wheelZoom: false,
|
|
||||||
reverse: true,
|
|
||||||
depth: 0.5,
|
|
||||||
maxSpeed: 0.2,
|
|
||||||
minSpeed: 0.003,
|
|
||||||
stretchX: 0.8,
|
|
||||||
stretchY: 0.8,
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
width = rootEl.offsetWidth;
|
|
||||||
|
|
||||||
if (loaded) {
|
|
||||||
available = true;
|
|
||||||
} else {
|
|
||||||
document.head
|
|
||||||
.appendChild(
|
|
||||||
Object.assign(document.createElement("script"), {
|
|
||||||
async: true,
|
|
||||||
src: "/client-assets/tagcanvas.min.js",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.addEventListener("load", () => (available = true));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (window.TagCanvas) window.TagCanvas.Delete(idForCanvas);
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
update: () => {
|
|
||||||
window.TagCanvas.Update(idForCanvas);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.meijqfqm {
|
|
||||||
position: relative;
|
|
||||||
overflow: clip;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
|
|
||||||
> .canvas {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .tags {
|
|
||||||
position: absolute;
|
|
||||||
top: 999px;
|
|
||||||
left: 999px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,64 +0,0 @@
|
||||||
<template>
|
|
||||||
<component
|
|
||||||
:is="'x-' + block.type"
|
|
||||||
:key="block.id"
|
|
||||||
:block="block"
|
|
||||||
:hpml="hpml"
|
|
||||||
:h="h"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from "vue";
|
|
||||||
import XText from "./page.text.vue";
|
|
||||||
import XSection from "./page.section.vue";
|
|
||||||
import XImage from "./page.image.vue";
|
|
||||||
import XButton from "./page.button.vue";
|
|
||||||
import XNumberInput from "./page.number-input.vue";
|
|
||||||
import XTextInput from "./page.text-input.vue";
|
|
||||||
import XTextareaInput from "./page.textarea-input.vue";
|
|
||||||
import XSwitch from "./page.switch.vue";
|
|
||||||
import XIf from "./page.if.vue";
|
|
||||||
import XTextarea from "./page.textarea.vue";
|
|
||||||
import XPost from "./page.post.vue";
|
|
||||||
import XCounter from "./page.counter.vue";
|
|
||||||
import XRadioButton from "./page.radio-button.vue";
|
|
||||||
import XCanvas from "./page.canvas.vue";
|
|
||||||
import XNote from "./page.note.vue";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { Block } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XText,
|
|
||||||
XSection,
|
|
||||||
XImage,
|
|
||||||
XButton,
|
|
||||||
XNumberInput,
|
|
||||||
XTextInput,
|
|
||||||
XTextareaInput,
|
|
||||||
XTextarea,
|
|
||||||
XPost,
|
|
||||||
XSwitch,
|
|
||||||
XIf,
|
|
||||||
XCounter,
|
|
||||||
XRadioButton,
|
|
||||||
XCanvas,
|
|
||||||
XNote,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<Block>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
h: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,70 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MkButton class="kudkigyw" :primary="block.primary" @click="click()">{{
|
|
||||||
hpml.interpolate(block.text)
|
|
||||||
}}</MkButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType, unref } from "vue";
|
|
||||||
import MkButton from "../MkButton.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { ButtonBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<ButtonBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
click() {
|
|
||||||
if (this.block.action === "dialog") {
|
|
||||||
this.hpml.eval();
|
|
||||||
os.alert({
|
|
||||||
text: this.hpml.interpolate(this.block.content),
|
|
||||||
});
|
|
||||||
} else if (this.block.action === "resetRandom") {
|
|
||||||
this.hpml.updateRandomSeed(Math.random());
|
|
||||||
this.hpml.eval();
|
|
||||||
} else if (this.block.action === "pushEvent") {
|
|
||||||
os.api("page-push", {
|
|
||||||
pageId: this.hpml.page.id,
|
|
||||||
event: this.block.event,
|
|
||||||
...(this.block.var
|
|
||||||
? {
|
|
||||||
var: unref(this.hpml.vars)[this.block.var],
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
os.alert({
|
|
||||||
type: "success",
|
|
||||||
text: this.hpml.interpolate(this.block.message),
|
|
||||||
});
|
|
||||||
} else if (this.block.action === "callAiScript") {
|
|
||||||
this.hpml.callAiScript(this.block.fn);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.kudkigyw {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 450px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,49 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="ysrxegms">
|
|
||||||
<canvas ref="canvas" :width="block.width" :height="block.height" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, PropType, Ref, ref } from "vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { CanvasBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<CanvasBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const canvas: Ref<any> = ref(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.hpml.registerCanvas(props.block.name, canvas.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvas,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ysrxegms {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: bottom;
|
|
||||||
overflow: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
> canvas {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,56 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MkButton class="llumlmnx" @click="click()">{{
|
|
||||||
hpml.interpolate(block.text)
|
|
||||||
}}</MkButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkButton from "../MkButton.vue";
|
|
||||||
import { CounterVarBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<CounterVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function click() {
|
|
||||||
props.hpml.updatePageVar(
|
|
||||||
props.block.name,
|
|
||||||
value.value + (props.block.inc || 1)
|
|
||||||
);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
click,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.llumlmnx {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 450px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-show="hpml.vars.value[block.var]">
|
|
||||||
<XBlock
|
|
||||||
v-for="child in block.children"
|
|
||||||
:key="child.id"
|
|
||||||
:block="child"
|
|
||||||
:hpml="hpml"
|
|
||||||
:h="h"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { IfBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { defineComponent, defineAsyncComponent, PropType } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XBlock: defineAsyncComponent(() => import("./page.block.vue")),
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<IfBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
h: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="lzyxtsnt">
|
|
||||||
<ImgWithBlurhash
|
|
||||||
v-if="image"
|
|
||||||
:hash="image.blurhash"
|
|
||||||
:src="image.url"
|
|
||||||
:alt="image.comment"
|
|
||||||
:title="image.comment"
|
|
||||||
:cover="false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineComponent, PropType } from "vue";
|
|
||||||
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { ImageBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
block: PropType<ImageBlock>;
|
|
||||||
hpml: PropType<Hpml>;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const image = props.hpml.page.attachedFiles.find(
|
|
||||||
(x) => x.id === props.block.fileId
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.lzyxtsnt {
|
|
||||||
> img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,61 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="voxdxuby">
|
|
||||||
<XNote
|
|
||||||
v-if="note && !block.detailed"
|
|
||||||
:key="note.id + ':normal'"
|
|
||||||
v-model:note="note"
|
|
||||||
/>
|
|
||||||
<XNoteDetailed
|
|
||||||
v-if="note && block.detailed"
|
|
||||||
:key="note.id + ':detail'"
|
|
||||||
v-model:note="note"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, PropType, Ref, ref } from "vue";
|
|
||||||
import XNote from "@/components/MagNote.vue";
|
|
||||||
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { NoteBlock } from "@/scripts/hpml/block";
|
|
||||||
import { endpoints, packed } from "magnetar-common";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XNote,
|
|
||||||
XNoteDetailed,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<NoteBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const note: Ref<packed.PackNoteMaybeFull | null> = ref(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!props.block.note) return;
|
|
||||||
|
|
||||||
os.magApi(
|
|
||||||
endpoints.GetNoteById,
|
|
||||||
{ context: props.block.detailed, attachments: true },
|
|
||||||
{ id: props.block.note }
|
|
||||||
).then((result) => {
|
|
||||||
note.value = result;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
note,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.voxdxuby {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MkInput
|
|
||||||
class="kudkigyw"
|
|
||||||
:model-value="value"
|
|
||||||
type="number"
|
|
||||||
@update:modelValue="updateValue($event)"
|
|
||||||
>
|
|
||||||
<template #label>{{ hpml.interpolate(block.text) }}</template>
|
|
||||||
</MkInput>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkInput from "../form/input.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { NumberInputVarBlock } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkInput,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<NumberInputVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateValue(newValue) {
|
|
||||||
props.hpml.updatePageVar(props.block.name, newValue);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
updateValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.kudkigyw {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 450px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,121 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="ngbfujlo">
|
|
||||||
<MkTextarea :model-value="text" readonly style="margin: 0"></MkTextarea>
|
|
||||||
<MkButton
|
|
||||||
class="button"
|
|
||||||
primary
|
|
||||||
:disabled="posting || posted"
|
|
||||||
@click="post()"
|
|
||||||
>
|
|
||||||
<i v-if="posted" class="ph-check ph-bold ph-lg"></i>
|
|
||||||
<i v-else class="ph-paper-plane-tilt ph-bold ph-lg"></i>
|
|
||||||
</MkButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from "vue";
|
|
||||||
import MkTextarea from "../form/textarea.vue";
|
|
||||||
import MkButton from "../MkButton.vue";
|
|
||||||
import { apiUrl } from "@/config";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { PostBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkTextarea,
|
|
||||||
MkButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<PostBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
text: this.hpml.interpolate(this.block.text),
|
|
||||||
posted: false,
|
|
||||||
posting: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
"hpml.vars": {
|
|
||||||
handler() {
|
|
||||||
this.text = this.hpml.interpolate(this.block.text);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
upload() {
|
|
||||||
const promise = new Promise((ok) => {
|
|
||||||
const canvas = this.hpml.canvases[this.block.canvasId];
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", blob);
|
|
||||||
if (this.$store.state.uploadFolder) {
|
|
||||||
formData.append(
|
|
||||||
"folderId",
|
|
||||||
this.$store.state.uploadFolder
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(apiUrl + "/drive/files/create", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${this.$i.token}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((f) => {
|
|
||||||
ok(f);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
os.promiseDialog(promise);
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
async post() {
|
|
||||||
this.posting = true;
|
|
||||||
const file = this.block.attachCanvasImage
|
|
||||||
? await this.upload()
|
|
||||||
: null;
|
|
||||||
os.apiWithDialog("notes/create", {
|
|
||||||
text: this.text === "" ? null : this.text,
|
|
||||||
fileIds: file ? [file.id] : undefined,
|
|
||||||
}).then(() => {
|
|
||||||
this.posted = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ngbfujlo {
|
|
||||||
position: relative;
|
|
||||||
padding: 32px;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 0 2px 8px var(--shadow);
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,52 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div>{{ hpml.interpolate(block.title) }}</div>
|
|
||||||
<MkRadio
|
|
||||||
v-for="item in block.values"
|
|
||||||
:key="item"
|
|
||||||
:modelValue="value"
|
|
||||||
:value="item"
|
|
||||||
@update:modelValue="updateValue($event)"
|
|
||||||
>{{ item }}</MkRadio
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkRadio from "../form/radio.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { RadioButtonVarBlock } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkRadio,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<RadioButtonVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateValue(newValue: string) {
|
|
||||||
props.hpml.updatePageVar(props.block.name, newValue);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
updateValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template>
|
|
||||||
<section class="sdgxphyu">
|
|
||||||
<component :is="'h' + h">{{ block.title }}</component>
|
|
||||||
|
|
||||||
<div class="children">
|
|
||||||
<XBlock
|
|
||||||
v-for="child in block.children"
|
|
||||||
:key="child.id"
|
|
||||||
:block="child"
|
|
||||||
:hpml="hpml"
|
|
||||||
:h="h + 1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, defineAsyncComponent, PropType } from "vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { SectionBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XBlock: defineAsyncComponent(() => import("./page.block.vue")),
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<SectionBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
h: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.sdgxphyu {
|
|
||||||
margin: 1.5em 0;
|
|
||||||
|
|
||||||
> h2 {
|
|
||||||
font-size: 1.35em;
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> h3 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> h4 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .children {
|
|
||||||
//padding 16px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,59 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="hkcxmtwj">
|
|
||||||
<MkSwitch
|
|
||||||
:model-value="value"
|
|
||||||
@update:modelValue="updateValue($event)"
|
|
||||||
>{{ hpml.interpolate(block.text) }}</MkSwitch
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkSwitch from "../form/switch.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { SwitchVarBlock } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkSwitch,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<SwitchVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateValue(newValue: boolean) {
|
|
||||||
props.hpml.updatePageVar(props.block.name, newValue);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
updateValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.hkcxmtwj {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 16px auto;
|
|
||||||
|
|
||||||
& + .hkcxmtwj {
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MkInput
|
|
||||||
class="kudkigyw"
|
|
||||||
:model-value="value"
|
|
||||||
type="text"
|
|
||||||
@update:modelValue="updateValue($event)"
|
|
||||||
>
|
|
||||||
<template #label>{{ hpml.interpolate(block.text) }}</template>
|
|
||||||
</MkInput>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkInput from "../form/input.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { TextInputVarBlock } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkInput,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<TextInputVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateValue(newValue) {
|
|
||||||
props.hpml.updatePageVar(props.block.name, newValue);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
updateValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.kudkigyw {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 450px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mrdgzndn">
|
|
||||||
<Mfm :key="text" :text="text" :is-note="false" :i="$i" />
|
|
||||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" class="url" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { TextBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { defineAsyncComponent, defineComponent, PropType } from "vue";
|
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkUrlPreview: defineAsyncComponent(
|
|
||||||
() => import("@/components/MkUrlPreview.vue")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<TextBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
text: this.hpml.interpolate(this.block.text),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
urls(): string[] {
|
|
||||||
if (this.text) {
|
|
||||||
return extractUrlFromMfm(mfm.parse(this.text));
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
"hpml.vars": {
|
|
||||||
handler() {
|
|
||||||
this.text = this.hpml.interpolate(this.block.text);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mrdgzndn {
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
> .url {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MkTextarea
|
|
||||||
:model-value="value"
|
|
||||||
@update:modelValue="updateValue($event)"
|
|
||||||
>
|
|
||||||
<template #label>{{ hpml.interpolate(block.text) }}</template>
|
|
||||||
</MkTextarea>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
|
||||||
import MkTextarea from "../form/textarea.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { HpmlTextInput } from "@/scripts/hpml";
|
|
||||||
import { TextInputVarBlock } from "@/scripts/hpml/block";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
MkTextarea,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as PropType<TextInputVarBlock>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
type: Object as PropType<Hpml>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const value = computed(() => {
|
|
||||||
return props.hpml.vars.value[props.block.name];
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateValue(newValue) {
|
|
||||||
props.hpml.updatePageVar(props.block.name, newValue);
|
|
||||||
props.hpml.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
updateValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,28 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkTextarea :model-value="text" readonly></MkTextarea>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { watch } from "vue";
|
|
||||||
import MkTextarea from "../form/textarea.vue";
|
|
||||||
import { TextBlock } from "@/scripts/hpml/block";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
block: TextBlock;
|
|
||||||
hpml: Hpml;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let text = $ref("");
|
|
||||||
|
|
||||||
watch(
|
|
||||||
props.hpml.vars,
|
|
||||||
() => {
|
|
||||||
text = props.hpml.interpolate(props.block.text) as string;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,104 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="hpml"
|
|
||||||
class="iroscrza"
|
|
||||||
:class="{ center: page.alignCenter, serif: page.font === 'serif' }"
|
|
||||||
>
|
|
||||||
<XBlock
|
|
||||||
v-for="child in page.content"
|
|
||||||
:key="child.id"
|
|
||||||
:block="child"
|
|
||||||
:hpml="hpml"
|
|
||||||
:h="2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
onUnmounted,
|
|
||||||
PropType,
|
|
||||||
} from "vue";
|
|
||||||
import { parse } from "@syuilo/aiscript";
|
|
||||||
import XBlock from "./page.block.vue";
|
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
|
||||||
import { url } from "@/config";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XBlock,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
page: {
|
|
||||||
type: Object as PropType<Record<string, any>>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
const hpml = new Hpml(props.page, {
|
|
||||||
randomSeed: Math.random(),
|
|
||||||
visitor: $i,
|
|
||||||
url: url,
|
|
||||||
enableAiScript: !defaultStore.state.disablePagesScript,
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
if (props.page.script && hpml.aiscript) {
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(props.page.script);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
/*os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: 'Syntax error :('
|
|
||||||
});*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hpml.aiscript
|
|
||||||
.exec(ast)
|
|
||||||
.then(() => {
|
|
||||||
hpml.eval();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
/*os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: err
|
|
||||||
});*/
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
hpml.eval();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (hpml.aiscript) hpml.aiscript.abort();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
hpml,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.iroscrza {
|
|
||||||
&.serif {
|
|
||||||
> div {
|
|
||||||
font-family: serif;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { Directive } from "vue";
|
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted(el, binding, vn) {
|
|
||||||
/*
|
|
||||||
if (!defaultStore.state.animation) return;
|
|
||||||
|
|
||||||
el.classList.add('_anime_bounce_standBy');
|
|
||||||
|
|
||||||
el.addEventListener('mousedown', () => {
|
|
||||||
el.classList.add('_anime_bounce_standBy');
|
|
||||||
el.classList.add('_anime_bounce_ready');
|
|
||||||
|
|
||||||
el.addEventListener('mouseleave', () => {
|
|
||||||
el.classList.remove('_anime_bounce_ready');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
el.classList.add('_anime_bounce');
|
|
||||||
});
|
|
||||||
|
|
||||||
el.addEventListener('animationend', () => {
|
|
||||||
el.classList.remove('_anime_bounce_ready');
|
|
||||||
el.classList.remove('_anime_bounce');
|
|
||||||
el.classList.add('_anime_bounce_standBy');
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
} as Directive;
|
|
|
@ -8,7 +8,6 @@ import tooltip from "./tooltip";
|
||||||
import hotkey from "./hotkey";
|
import hotkey from "./hotkey";
|
||||||
import appear from "./appear";
|
import appear from "./appear";
|
||||||
import anim from "./anim";
|
import anim from "./anim";
|
||||||
import clickAnime from "./click-anime";
|
|
||||||
import panel from "./panel";
|
import panel from "./panel";
|
||||||
import adaptiveBorder from "./adaptive-border";
|
import adaptiveBorder from "./adaptive-border";
|
||||||
import focus from "./focus";
|
import focus from "./focus";
|
||||||
|
@ -23,7 +22,6 @@ export default function (app: App) {
|
||||||
app.directive("hotkey", hotkey);
|
app.directive("hotkey", hotkey);
|
||||||
app.directive("appear", appear);
|
app.directive("appear", appear);
|
||||||
app.directive("anim", anim);
|
app.directive("anim", anim);
|
||||||
app.directive("click-anime", clickAnime);
|
|
||||||
app.directive("panel", panel);
|
app.directive("panel", panel);
|
||||||
app.directive("adaptive-border", adaptiveBorder);
|
app.directive("adaptive-border", adaptiveBorder);
|
||||||
app.directive("focus", focus);
|
app.directive("focus", focus);
|
||||||
|
|
|
@ -437,14 +437,6 @@ function checkForSplash() {
|
||||||
//store.commit('instance/set', );
|
//store.commit('instance/set', );
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const plugin of ColdDeviceStorage.get("plugins").filter(
|
|
||||||
(p) => p.active
|
|
||||||
)) {
|
|
||||||
import("./plugin").then(({ install }) => {
|
|
||||||
install(plugin);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hotkeys = {
|
const hotkeys = {
|
||||||
d: (): void => {
|
d: (): void => {
|
||||||
defaultStore.set("darkMode", !defaultStore.state.darkMode);
|
defaultStore.set("darkMode", !defaultStore.state.darkMode);
|
||||||
|
|
|
@ -63,11 +63,6 @@ export const navbarItemDef = reactive({
|
||||||
show: computed(() => $i != null),
|
show: computed(() => $i != null),
|
||||||
to: "/my/favorites",
|
to: "/my/favorites",
|
||||||
},
|
},
|
||||||
pages: {
|
|
||||||
title: "pages",
|
|
||||||
icon: "ph-file-text ph-bold ph-lg",
|
|
||||||
to: "/pages",
|
|
||||||
},
|
|
||||||
gallery: {
|
gallery: {
|
||||||
title: "gallery",
|
title: "gallery",
|
||||||
icon: "ph-image-square ph-bold ph-lg",
|
icon: "ph-image-square ph-bold ph-lg",
|
||||||
|
|
|
@ -60,13 +60,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { markRaw, nextTick, onBeforeUnmount, onMounted } from "vue";
|
||||||
markRaw,
|
|
||||||
version as vueVersion,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUnmount,
|
|
||||||
nextTick,
|
|
||||||
} from "vue";
|
|
||||||
import XFederation from "./overview.federation.vue";
|
import XFederation from "./overview.federation.vue";
|
||||||
import XInstances from "./overview.instances.vue";
|
import XInstances from "./overview.instances.vue";
|
||||||
import XQueue from "./overview.queue.vue";
|
import XQueue from "./overview.queue.vue";
|
||||||
|
@ -77,14 +71,10 @@ import XStats from "./overview.stats.vue";
|
||||||
import XModerators from "./overview.moderators.vue";
|
import XModerators from "./overview.moderators.vue";
|
||||||
import XHeatmap from "./overview.heatmap.vue";
|
import XHeatmap from "./overview.heatmap.vue";
|
||||||
// import XMetrics from "./overview.metrics.vue";
|
// import XMetrics from "./overview.metrics.vue";
|
||||||
import MkTagCloud from "@/components/MkTagCloud.vue";
|
|
||||||
import { version, url } from "@/config";
|
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import MkFileListForAdmin from "@/components/MkFileListForAdmin.vue";
|
|
||||||
import MkFolder from "@/components/MkFolder.vue";
|
import MkFolder from "@/components/MkFolder.vue";
|
||||||
|
|
||||||
const rootEl = $shallowRef<HTMLElement>();
|
const rootEl = $shallowRef<HTMLElement>();
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
<button
|
<button
|
||||||
v-if="$i && $i.id === post.user.id"
|
v-if="$i && $i.id === post.user.id"
|
||||||
v-tooltip="i18n.ts.edit"
|
v-tooltip="i18n.ts.edit"
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="edit"
|
@click="edit"
|
||||||
>
|
>
|
||||||
|
@ -70,7 +69,6 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-tooltip="i18n.ts.shareWithNote"
|
v-tooltip="i18n.ts.shareWithNote"
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="shareWithNote"
|
@click="shareWithNote"
|
||||||
>
|
>
|
||||||
|
@ -81,7 +79,6 @@
|
||||||
<button
|
<button
|
||||||
v-if="shareAvailable()"
|
v-if="shareAvailable()"
|
||||||
v-tooltip="i18n.ts.share"
|
v-tooltip="i18n.ts.share"
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="share"
|
@click="share"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.button }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section class="xfhsjczc">
|
|
||||||
<MkInput v-model="value.text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button.text
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkSwitch v-model="value.primary"
|
|
||||||
><span>{{
|
|
||||||
i18n.ts._pages.blocks._button.colored
|
|
||||||
}}</span></MkSwitch
|
|
||||||
>
|
|
||||||
<MkSelect v-model="value.action">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button.action
|
|
||||||
}}</template>
|
|
||||||
<option value="dialog">
|
|
||||||
{{ i18n.ts._pages.blocks._button._action.dialog }}
|
|
||||||
</option>
|
|
||||||
<option value="resetRandom">
|
|
||||||
{{ i18n.ts._pages.blocks._button._action.resetRandom }}
|
|
||||||
</option>
|
|
||||||
<option value="pushEvent">
|
|
||||||
{{ i18n.ts._pages.blocks._button._action.pushEvent }}
|
|
||||||
</option>
|
|
||||||
<option value="callAiScript">
|
|
||||||
{{ i18n.ts._pages.blocks._button._action.callAiScript }}
|
|
||||||
</option>
|
|
||||||
</MkSelect>
|
|
||||||
<template v-if="value.action === 'dialog'">
|
|
||||||
<MkInput v-model="value.content"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button._action._dialog.content
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="value.action === 'pushEvent'">
|
|
||||||
<MkInput v-model="value.event"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button._action._pushEvent.event
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.message"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button._action._pushEvent.message
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkSelect v-model="value.var">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button._action._pushEvent
|
|
||||||
.variable
|
|
||||||
}}</template>
|
|
||||||
<option :value="null">
|
|
||||||
{{
|
|
||||||
i18n.t(
|
|
||||||
"_pages.blocks._button._action._pushEvent.no-variable"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</option>
|
|
||||||
<option v-for="v in hpml.getVarsByType()" :value="v.name">
|
|
||||||
{{ v.name }}
|
|
||||||
</option>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.pageVariables">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getPageVarsByType()"
|
|
||||||
:value="v"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup
|
|
||||||
:label="i18n.ts._pages.script.enviromentVariables"
|
|
||||||
>
|
|
||||||
<option v-for="v in hpml.getEnvVarsByType()" :value="v">
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
</MkSelect>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="value.action === 'callAiScript'">
|
|
||||||
<MkInput v-model="value.fn"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._button._action._callAiScript
|
|
||||||
.functionName
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkSelect from "@/components/form/select.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
hpml: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
text: "",
|
|
||||||
action: "dialog",
|
|
||||||
content: null,
|
|
||||||
event: null,
|
|
||||||
message: null,
|
|
||||||
primary: false,
|
|
||||||
var: null,
|
|
||||||
fn: null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.xfhsjczc {
|
|
||||||
padding: 0 16px 0 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-paint-brush-household ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.canvas }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 0 16px">
|
|
||||||
<MkInput v-model="value.name">
|
|
||||||
<template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i
|
|
||||||
></template>
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._canvas.id
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.width" type="number">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._canvas.width
|
|
||||||
}}</template>
|
|
||||||
<template #suffix>px</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.height" type="number">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._canvas.height
|
|
||||||
}}</template>
|
|
||||||
<template #suffix>px</template>
|
|
||||||
</MkInput>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
width: 300,
|
|
||||||
height: 200,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,47 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.counter }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 0 16px">
|
|
||||||
<MkInput v-model="value.name">
|
|
||||||
<template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i
|
|
||||||
></template>
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._counter.name
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.text">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._counter.text
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.inc" type="number">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._counter.inc
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,88 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-question ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.if }}</template
|
|
||||||
>
|
|
||||||
<template #func>
|
|
||||||
<button class="_button" @click="add()">
|
|
||||||
<i class="ph-plus ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<section class="romcojzs">
|
|
||||||
<MkSelect v-model="value.var">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._if.variable
|
|
||||||
}}</template>
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getVarsByType('boolean')"
|
|
||||||
:value="v.name"
|
|
||||||
>
|
|
||||||
{{ v.name }}
|
|
||||||
</option>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.pageVariables">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getPageVarsByType('boolean')"
|
|
||||||
:value="v"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.enviromentVariables">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getEnvVarsByType('boolean')"
|
|
||||||
:value="v"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
</MkSelect>
|
|
||||||
|
|
||||||
<XBlocks v-model="value.children" class="children" :hpml="hpml" />
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineAsyncComponent, inject } from "vue";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkSelect from "@/components/form/select.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const XBlocks = defineAsyncComponent(() => import("../page-editor.blocks.vue"));
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
hpml: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
children: [],
|
|
||||||
var: null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const getPageBlockList = inject<(any) => any>("getPageBlockList");
|
|
||||||
|
|
||||||
async function add() {
|
|
||||||
const { canceled, result: type } = await os.select({
|
|
||||||
title: i18n.ts._pages.chooseBlock,
|
|
||||||
groupedItems: getPageBlockList(),
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const id = uuid();
|
|
||||||
props.value.children.push({ id, type });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.romcojzs {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,71 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-image ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.image }}</template
|
|
||||||
>
|
|
||||||
<template #func>
|
|
||||||
<button @click="choose()">
|
|
||||||
<i class="ph-folder-notch-open ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<section class="oyyftmcf">
|
|
||||||
<MkDriveFileThumbnail
|
|
||||||
v-if="file"
|
|
||||||
class="preview"
|
|
||||||
:file="file"
|
|
||||||
fit="contain"
|
|
||||||
@click="choose()"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
fileId: null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let file: any = $ref(null);
|
|
||||||
|
|
||||||
async function choose() {
|
|
||||||
os.selectDriveFile(false).then((fileResponse: any) => {
|
|
||||||
file = fileResponse;
|
|
||||||
props.value.fileId = fileResponse.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (props.value.fileId == null) {
|
|
||||||
await choose();
|
|
||||||
} else {
|
|
||||||
os.api("drive/files/show", {
|
|
||||||
fileId: props.value.fileId,
|
|
||||||
}).then((fileResponse) => {
|
|
||||||
file = fileResponse;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.oyyftmcf {
|
|
||||||
> .preview {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,91 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-sticker ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.note }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 0 16px">
|
|
||||||
<MkInput v-model="id">
|
|
||||||
<template #label>{{ i18n.ts._pages.blocks._note.id }}</template>
|
|
||||||
<template #caption>{{
|
|
||||||
i18n.ts._pages.blocks._note.idDescription
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkSwitch v-model="value.detailed"
|
|
||||||
><span>{{
|
|
||||||
i18n.ts._pages.blocks._note.detailed
|
|
||||||
}}</span></MkSwitch
|
|
||||||
>
|
|
||||||
|
|
||||||
<XNote
|
|
||||||
v-if="note && !value.detailed"
|
|
||||||
:key="note.id + ':normal'"
|
|
||||||
v-model:note="note"
|
|
||||||
style="margin-bottom: 16px"
|
|
||||||
/>
|
|
||||||
<XNoteDetailed
|
|
||||||
v-if="note && value.detailed"
|
|
||||||
:key="note.id + ':detail'"
|
|
||||||
v-model:note="note"
|
|
||||||
style="margin-bottom: 16px"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
|
||||||
import XNote from "@/components/MagNote.vue";
|
|
||||||
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { endpoints, packed } from "magnetar-common";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
note: null,
|
|
||||||
detailed: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let id = ref<string | null>(props.value.note);
|
|
||||||
let note = ref<packed.PackNoteMaybeFull | null>(null);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
id,
|
|
||||||
async () => {
|
|
||||||
if (
|
|
||||||
id.value &&
|
|
||||||
(id.value.startsWith("http://") || id.value.startsWith("https://"))
|
|
||||||
) {
|
|
||||||
props.value.note = (
|
|
||||||
id.value.endsWith("/") ? id.value.slice(0, -1) : id.value
|
|
||||||
)
|
|
||||||
.split("/")
|
|
||||||
.pop();
|
|
||||||
} else {
|
|
||||||
props.value.note = id.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.value.note) return;
|
|
||||||
|
|
||||||
note.value = await os.magApi(
|
|
||||||
endpoints.GetNoteById,
|
|
||||||
{ context: true, attachments: true },
|
|
||||||
{ id: props.value.note }
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,47 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.numberInput }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 0 16px">
|
|
||||||
<MkInput v-model="value.name">
|
|
||||||
<template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i
|
|
||||||
></template>
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._numberInput.name
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.text">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._numberInput.text
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="value.default" type="number">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.blocks._numberInput.default
|
|
||||||
}}</template>
|
|
||||||
</MkInput>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-paper-plane-tilt ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.post }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 16px">
|
|
||||||
<MkTextarea v-model="value.text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._post.text
|
|
||||||
}}</template></MkTextarea
|
|
||||||
>
|
|
||||||
<MkSwitch v-model="value.attachCanvasImage"
|
|
||||||
><span>{{
|
|
||||||
i18n.ts._pages.blocks._post.attachCanvasImage
|
|
||||||
}}</span></MkSwitch
|
|
||||||
>
|
|
||||||
<MkInput v-if="value.attachCanvasImage" v-model="value.canvasId"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._post.canvasId
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkTextarea from "@/components/form/textarea.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
text: "",
|
|
||||||
attachCanvasImage: false,
|
|
||||||
canvasId: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.radioButton }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 16px 16px">
|
|
||||||
<MkInput v-model="value.name"
|
|
||||||
><template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i></template
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._radioButton.name
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.title"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._radioButton.title
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkTextarea v-model="values"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._radioButton.values
|
|
||||||
}}</template></MkTextarea
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.default"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._radioButton.default
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { watch } from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkTextarea from "@/components/form/textarea.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
title: "",
|
|
||||||
values: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let values: string = $ref(props.value.values.join("\n"));
|
|
||||||
|
|
||||||
watch(
|
|
||||||
values,
|
|
||||||
() => {
|
|
||||||
props.value.values = values.split("\n");
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,79 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-sticker ph-bold ph-lg"></i>
|
|
||||||
{{ value.title }}</template
|
|
||||||
>
|
|
||||||
<template #func>
|
|
||||||
<button class="_button" @click="rename()">
|
|
||||||
<i class="ph-pencil ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<button class="_button" @click="add()">
|
|
||||||
<i class="ph-plus ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<section class="ilrvjyvi">
|
|
||||||
<XBlocks v-model="value.children" class="children" :hpml="hpml" />
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineAsyncComponent, inject, onMounted } from "vue";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const XBlocks = defineAsyncComponent(() => import("../page-editor.blocks.vue"));
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
hpml: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
title: null,
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const getPageBlockList = inject<(any) => any>("getPageBlockList");
|
|
||||||
|
|
||||||
async function rename() {
|
|
||||||
const { canceled, result: title } = await os.inputText({
|
|
||||||
title: "Enter title",
|
|
||||||
default: props.value.title,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
props.value.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function add() {
|
|
||||||
const { canceled, result: type } = await os.select({
|
|
||||||
title: i18n.ts._pages.chooseBlock,
|
|
||||||
groupedItems: getPageBlockList(),
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const id = uuid();
|
|
||||||
props.value.children.push({ id, type });
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.value.title == null) {
|
|
||||||
rename();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ilrvjyvi {
|
|
||||||
> .children {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.switch }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section class="kjuadyyj">
|
|
||||||
<MkInput v-model="value.name"
|
|
||||||
><template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i></template
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._switch.name
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._switch.text
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkSwitch v-model="value.default"
|
|
||||||
><span>{{
|
|
||||||
i18n.ts._pages.blocks._switch.default
|
|
||||||
}}</span></MkSwitch
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.kjuadyyj {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.textInput }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 0 16px">
|
|
||||||
<MkInput v-model="value.name"
|
|
||||||
><template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i></template
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textInput.name
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textInput.text
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.default" type="text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textInput.default
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-align-left ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.text }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section class="vckmsadr">
|
|
||||||
<textarea v-model="value.text"></textarea>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
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>
|
|
|
@ -1,47 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.textareaInput }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section style="padding: 0 16px 16px 16px">
|
|
||||||
<MkInput v-model="value.name"
|
|
||||||
><template #prefix
|
|
||||||
><i class="ph-magic-wand ph-bold ph-lg"></i></template
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textareaInput.name
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkInput v-model="value.text"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textareaInput.text
|
|
||||||
}}</template></MkInput
|
|
||||||
>
|
|
||||||
<MkTextarea v-model="value.default"
|
|
||||||
><template #label>{{
|
|
||||||
i18n.ts._pages.blocks._textareaInput.default
|
|
||||||
}}</template></MkTextarea
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import MkTextarea from "@/components/form/textarea.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-align-left ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.blocks.textarea }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<section class="ihymsbbe">
|
|
||||||
<textarea v-model="value.text"></textarea>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XContainer from "../page-editor.container.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
value: any;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
text: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ihymsbbe {
|
|
||||||
> 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>
|
|
|
@ -1,108 +0,0 @@
|
||||||
<template>
|
|
||||||
<XDraggable
|
|
||||||
v-model="blocks"
|
|
||||||
tag="div"
|
|
||||||
item-key="id"
|
|
||||||
handle=".drag-handle"
|
|
||||||
:group="{ name: 'blocks' }"
|
|
||||||
animation="150"
|
|
||||||
swap-threshold="0.5"
|
|
||||||
>
|
|
||||||
<template #item="{ element }">
|
|
||||||
<component
|
|
||||||
:is="'x-' + element.type"
|
|
||||||
:value="element"
|
|
||||||
:hpml="hpml"
|
|
||||||
@update:value="updateItem"
|
|
||||||
@remove="() => removeItem(element)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</XDraggable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, defineAsyncComponent } from "vue";
|
|
||||||
import XSection from "./els/page-editor.el.section.vue";
|
|
||||||
import XText from "./els/page-editor.el.text.vue";
|
|
||||||
import XTextarea from "./els/page-editor.el.textarea.vue";
|
|
||||||
import XImage from "./els/page-editor.el.image.vue";
|
|
||||||
import XButton from "./els/page-editor.el.button.vue";
|
|
||||||
import XTextInput from "./els/page-editor.el.text-input.vue";
|
|
||||||
import XTextareaInput from "./els/page-editor.el.textarea-input.vue";
|
|
||||||
import XNumberInput from "./els/page-editor.el.number-input.vue";
|
|
||||||
import XSwitch from "./els/page-editor.el.switch.vue";
|
|
||||||
import XIf from "./els/page-editor.el.if.vue";
|
|
||||||
import XPost from "./els/page-editor.el.post.vue";
|
|
||||||
import XCounter from "./els/page-editor.el.counter.vue";
|
|
||||||
import XRadioButton from "./els/page-editor.el.radio-button.vue";
|
|
||||||
import XCanvas from "./els/page-editor.el.canvas.vue";
|
|
||||||
import XNote from "./els/page-editor.el.note.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XDraggable: defineAsyncComponent(() =>
|
|
||||||
import("vuedraggable").then((x) => x.default)
|
|
||||||
),
|
|
||||||
XSection,
|
|
||||||
XText,
|
|
||||||
XImage,
|
|
||||||
XButton,
|
|
||||||
XTextarea,
|
|
||||||
XTextInput,
|
|
||||||
XTextareaInput,
|
|
||||||
XNumberInput,
|
|
||||||
XSwitch,
|
|
||||||
XIf,
|
|
||||||
XPost,
|
|
||||||
XCounter,
|
|
||||||
XRadioButton,
|
|
||||||
XCanvas,
|
|
||||||
XNote,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
blocks: {
|
|
||||||
get() {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$emit("update:modelValue", value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
updateItem(v) {
|
|
||||||
const i = this.blocks.findIndex((x) => x.id === v.id);
|
|
||||||
const newValue = [
|
|
||||||
...this.blocks.slice(0, i),
|
|
||||||
v,
|
|
||||||
...this.blocks.slice(i + 1),
|
|
||||||
];
|
|
||||||
this.$emit("update:modelValue", newValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeItem(el) {
|
|
||||||
const i = this.blocks.findIndex((x) => x.id === el.id);
|
|
||||||
const newValue = [
|
|
||||||
...this.blocks.slice(0, i),
|
|
||||||
...this.blocks.slice(i + 1),
|
|
||||||
];
|
|
||||||
this.$emit("update:modelValue", newValue);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,180 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
|
|
||||||
<header>
|
|
||||||
<div class="title"><slot name="header"></slot></div>
|
|
||||||
<div class="buttons">
|
|
||||||
<slot name="func"></slot>
|
|
||||||
<button v-if="removable" class="_button" @click="remove()">
|
|
||||||
<i class="ph-trash ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<button v-if="draggable" class="drag-handle _button">
|
|
||||||
<i class="ph-list ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<button class="_button" @click="toggleContent(!showBody)">
|
|
||||||
<template v-if="showBody"
|
|
||||||
><i class="ph-caret-up ph-bold ph-lg"></i
|
|
||||||
></template>
|
|
||||||
<template v-else
|
|
||||||
><i class="ph-caret-down ph-bold ph-lg"></i
|
|
||||||
></template>
|
|
||||||
</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";
|
|
||||||
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");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cpjygsrt {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--panel);
|
|
||||||
border: solid 2px var(--X12);
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: solid 2px var(--X13);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warn {
|
|
||||||
border: solid 2px #f6c177;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
border: solid 2px #eb6f92;
|
|
||||||
}
|
|
||||||
|
|
||||||
& + .cpjygsrt {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> header {
|
|
||||||
> .title {
|
|
||||||
z-index: 1;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 16px;
|
|
||||||
line-height: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 1px rgba(#000, 0.07);
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .buttons {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 0;
|
|
||||||
width: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
line-height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .warn {
|
|
||||||
color: #ea9d34;
|
|
||||||
margin: 0;
|
|
||||||
padding: 16px 16px 0 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .error {
|
|
||||||
color: #b4637a;
|
|
||||||
margin: 0;
|
|
||||||
padding: 16px 16px 0 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
|
||||||
::v-deep(.juejbjww),
|
|
||||||
::v-deep(.eiipwacr) {
|
|
||||||
&:not(.inline):first-child {
|
|
||||||
margin-top: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.inline):last-child {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,408 +0,0 @@
|
||||||
<template>
|
|
||||||
<XContainer
|
|
||||||
:removable="removable"
|
|
||||||
:error="error"
|
|
||||||
:warn="warn"
|
|
||||||
:draggable="draggable"
|
|
||||||
@remove="() => $emit('remove')"
|
|
||||||
>
|
|
||||||
<template #header
|
|
||||||
><i v-if="icon" :class="icon"></i>
|
|
||||||
<template v-if="title"
|
|
||||||
>{{ title }}
|
|
||||||
<span v-if="typeText" class="turmquns"
|
|
||||||
>({{ typeText }})</span
|
|
||||||
></template
|
|
||||||
><template v-else-if="typeText">{{ typeText }}</template></template
|
|
||||||
>
|
|
||||||
<template #func>
|
|
||||||
<button class="_button" @click="changeType()">
|
|
||||||
<i class="ph-pencil ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<section
|
|
||||||
v-if="modelValue.type === null"
|
|
||||||
class="pbglfege"
|
|
||||||
@click="changeType()"
|
|
||||||
>
|
|
||||||
{{ i18n.ts._pages.script.emptySlot }}
|
|
||||||
</section>
|
|
||||||
<section v-else-if="modelValue.type === 'text'" class="tbwccoaw">
|
|
||||||
<input v-model="modelValue.value" />
|
|
||||||
</section>
|
|
||||||
<section
|
|
||||||
v-else-if="modelValue.type === 'multiLineText'"
|
|
||||||
class="tbwccoaw"
|
|
||||||
>
|
|
||||||
<textarea v-model="modelValue.value"></textarea>
|
|
||||||
</section>
|
|
||||||
<section v-else-if="modelValue.type === 'textList'" class="tbwccoaw">
|
|
||||||
<textarea
|
|
||||||
v-model="modelValue.value"
|
|
||||||
:placeholder="i18n.ts._pages.script.blocks._textList.info"
|
|
||||||
></textarea>
|
|
||||||
</section>
|
|
||||||
<section v-else-if="modelValue.type === 'number'" class="tbwccoaw">
|
|
||||||
<input v-model="modelValue.value" type="number" />
|
|
||||||
</section>
|
|
||||||
<section v-else-if="modelValue.type === 'ref'" class="hpdwcrvs">
|
|
||||||
<select v-model="modelValue.value">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml
|
|
||||||
.getVarsByType(
|
|
||||||
getExpectedType ? getExpectedType() : null
|
|
||||||
)
|
|
||||||
.filter((x) => x.name !== name)"
|
|
||||||
:value="v.name"
|
|
||||||
>
|
|
||||||
{{ v.name }}
|
|
||||||
</option>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.argVariables">
|
|
||||||
<option v-for="v in fnSlots" :value="v.name">
|
|
||||||
{{ v.name }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.pageVariables">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getPageVarsByType(
|
|
||||||
getExpectedType ? getExpectedType() : null
|
|
||||||
)"
|
|
||||||
:value="v"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup :label="i18n.ts._pages.script.enviromentVariables">
|
|
||||||
<option
|
|
||||||
v-for="v in hpml.getEnvVarsByType(
|
|
||||||
getExpectedType ? getExpectedType() : null
|
|
||||||
)"
|
|
||||||
:value="v"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section v-else-if="modelValue.type === 'aiScriptVar'" class="tbwccoaw">
|
|
||||||
<input v-model="modelValue.value" />
|
|
||||||
</section>
|
|
||||||
<section
|
|
||||||
v-else-if="modelValue.type === 'fn'"
|
|
||||||
class=""
|
|
||||||
style="padding: 0 16px 16px 16px"
|
|
||||||
>
|
|
||||||
<MkTextarea v-model="slots">
|
|
||||||
<template #label>{{
|
|
||||||
i18n.ts._pages.script.blocks._fn.slots
|
|
||||||
}}</template>
|
|
||||||
<template #caption>{{
|
|
||||||
i18n.t("_pages.script.blocks._fn.slots-info")
|
|
||||||
}}</template>
|
|
||||||
</MkTextarea>
|
|
||||||
<XV
|
|
||||||
v-if="modelValue.value.expression"
|
|
||||||
v-model="modelValue.value.expression"
|
|
||||||
:title="i18n.t(`_pages.script.blocks._fn.arg1`)"
|
|
||||||
:get-expected-type="() => null"
|
|
||||||
:hpml="hpml"
|
|
||||||
:fn-slots="modelValue.value.slots"
|
|
||||||
:name="name"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<section
|
|
||||||
v-else-if="modelValue.type.startsWith('fn:')"
|
|
||||||
class=""
|
|
||||||
style="padding: 16px"
|
|
||||||
>
|
|
||||||
<XV
|
|
||||||
v-for="(x, i) in modelValue.args"
|
|
||||||
:key="i"
|
|
||||||
v-model="modelValue.args[i]"
|
|
||||||
:title="
|
|
||||||
hpml.getVarByName(modelValue.type.split(':')[1]).value
|
|
||||||
.slots[i].name
|
|
||||||
"
|
|
||||||
:get-expected-type="() => null"
|
|
||||||
:hpml="hpml"
|
|
||||||
:name="name"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<section v-else class="" style="padding: 16px">
|
|
||||||
<XV
|
|
||||||
v-for="(x, i) in modelValue.args"
|
|
||||||
:key="i"
|
|
||||||
v-model="modelValue.args[i]"
|
|
||||||
:title="
|
|
||||||
i18n.t(
|
|
||||||
`_pages.script.blocks._${modelValue.type}.arg${i + 1}`
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:get-expected-type="() => _getExpectedType(i)"
|
|
||||||
:hpml="hpml"
|
|
||||||
:name="name"
|
|
||||||
:fn-slots="fnSlots"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</XContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineAsyncComponent, defineComponent } from "vue";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import XContainer from "./page-editor.container.vue";
|
|
||||||
import MkTextarea from "@/components/form/textarea.vue";
|
|
||||||
import { blockDefs } from "@/scripts/hpml/index";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { isLiteralValue } from "@/scripts/hpml/expr";
|
|
||||||
import { funcDefs } from "@/scripts/hpml/lib";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XContainer,
|
|
||||||
MkTextarea,
|
|
||||||
XV: defineAsyncComponent(
|
|
||||||
() => import("./page-editor.script-block.vue")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
inject: ["getScriptBlockList"],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
getExpectedType: {
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
removable: {
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hpml: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fnSlots: {
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
draggable: {
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
warn: null,
|
|
||||||
slots: "",
|
|
||||||
i18n,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
icon(): any {
|
|
||||||
if (this.modelValue.type === null) return null;
|
|
||||||
if (this.modelValue.type.startsWith("fn:"))
|
|
||||||
return "ph-plug ph-bold ph-lg";
|
|
||||||
return blockDefs.find((x) => x.type === this.modelValue.type).icon;
|
|
||||||
},
|
|
||||||
typeText(): any {
|
|
||||||
if (this.modelValue.type === null) return null;
|
|
||||||
if (this.modelValue.type.startsWith("fn:"))
|
|
||||||
return this.modelValue.type.split(":")[1];
|
|
||||||
return i18n.t(`_pages.script.blocks.${this.modelValue.type}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
slots: {
|
|
||||||
handler() {
|
|
||||||
this.modelValue.value.slots = this.slots
|
|
||||||
.split("\n")
|
|
||||||
.map((x) => ({
|
|
||||||
name: x,
|
|
||||||
type: null,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
if (this.modelValue.value == null) this.modelValue.value = null;
|
|
||||||
|
|
||||||
if (this.modelValue.value && this.modelValue.value.slots)
|
|
||||||
this.slots = this.modelValue.value.slots
|
|
||||||
.map((x) => x.name)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
this.$watch(
|
|
||||||
() => this.modelValue.type,
|
|
||||||
(t) => {
|
|
||||||
this.warn = null;
|
|
||||||
|
|
||||||
if (this.modelValue.type === "fn") {
|
|
||||||
const id = uuid();
|
|
||||||
this.modelValue.value = {
|
|
||||||
slots: [],
|
|
||||||
expression: { id, type: null },
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.modelValue.type &&
|
|
||||||
this.modelValue.type.startsWith("fn:")
|
|
||||||
) {
|
|
||||||
const fnName = this.modelValue.type.split(":")[1];
|
|
||||||
const fn = this.hpml.getVarByName(fnName);
|
|
||||||
|
|
||||||
const empties = [];
|
|
||||||
for (let i = 0; i < fn.value.slots.length; i++) {
|
|
||||||
const id = uuid();
|
|
||||||
empties.push({ id, type: null });
|
|
||||||
}
|
|
||||||
this.modelValue.args = empties;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLiteralValue(this.modelValue)) return;
|
|
||||||
|
|
||||||
const empties = [];
|
|
||||||
for (
|
|
||||||
let i = 0;
|
|
||||||
i < funcDefs[this.modelValue.type].in.length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
const id = uuid();
|
|
||||||
empties.push({ id, type: null });
|
|
||||||
}
|
|
||||||
this.modelValue.args = empties;
|
|
||||||
|
|
||||||
for (
|
|
||||||
let i = 0;
|
|
||||||
i < funcDefs[this.modelValue.type].in.length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
const inType = funcDefs[this.modelValue.type].in[i];
|
|
||||||
if (typeof inType !== "number") {
|
|
||||||
if (inType === "number")
|
|
||||||
this.modelValue.args[i].type = "number";
|
|
||||||
if (inType === "string")
|
|
||||||
this.modelValue.args[i].type = "text";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$watch(
|
|
||||||
() => this.modelValue.args,
|
|
||||||
(args) => {
|
|
||||||
if (args == null) {
|
|
||||||
this.warn = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const emptySlotIndex = args.findIndex((x) => x.type === null);
|
|
||||||
if (emptySlotIndex !== -1 && emptySlotIndex < args.length) {
|
|
||||||
this.warn = {
|
|
||||||
slot: emptySlotIndex,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.warn = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$watch(
|
|
||||||
() => this.hpml.variables,
|
|
||||||
() => {
|
|
||||||
if (this.type != null && this.modelValue) {
|
|
||||||
this.error = this.hpml.typeCheck(this.modelValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async changeType() {
|
|
||||||
const { canceled, result: type } = await os.select({
|
|
||||||
title: i18n.ts._pages.selectType,
|
|
||||||
groupedItems: this.getScriptBlockList(
|
|
||||||
this.getExpectedType ? this.getExpectedType() : null
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
this.modelValue.type = type;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getExpectedType(slot: number) {
|
|
||||||
return this.hpml.getExpectedType(this.modelValue, slot);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.turmquns {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pbglfege {
|
|
||||||
opacity: 0.5;
|
|
||||||
padding: 16px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tbwccoaw {
|
|
||||||
> input,
|
|
||||||
> textarea {
|
|
||||||
display: block;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--fg);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
> textarea {
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hpdwcrvs {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> select {
|
|
||||||
display: block;
|
|
||||||
padding: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,645 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header
|
|
||||||
><MkPageHeader
|
|
||||||
v-model:tab="tab"
|
|
||||||
:actions="headerActions"
|
|
||||||
:tabs="headerTabs"
|
|
||||||
/></template>
|
|
||||||
<MkSpacer :content-max="700">
|
|
||||||
<div class="jqqmcavi">
|
|
||||||
<MkButton
|
|
||||||
v-if="pageId"
|
|
||||||
class="button"
|
|
||||||
inline
|
|
||||||
link
|
|
||||||
:to="`/@${author.username}/pages/${currentName}`"
|
|
||||||
><i class="ph-arrow-square-out ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.viewPage }}</MkButton
|
|
||||||
>
|
|
||||||
<MkButton
|
|
||||||
v-if="!readonly"
|
|
||||||
inline
|
|
||||||
primary
|
|
||||||
class="button"
|
|
||||||
@click="save"
|
|
||||||
><i class="ph-floppy-disk-back ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.save }}</MkButton
|
|
||||||
>
|
|
||||||
<MkButton v-if="pageId" inline class="button" @click="duplicate"
|
|
||||||
><i class="ph-clipboard-text ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.duplicate }}</MkButton
|
|
||||||
>
|
|
||||||
<MkButton
|
|
||||||
v-if="pageId && !readonly"
|
|
||||||
inline
|
|
||||||
class="button"
|
|
||||||
danger
|
|
||||||
@click="del"
|
|
||||||
><i class="ph-trash ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.delete }}</MkButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="tab === 'settings'">
|
|
||||||
<div class="_formRoot">
|
|
||||||
<MkInput v-model="title" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._pages.title }}</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput v-model="summary" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._pages.summary }}</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput v-model="name" class="_formBlock">
|
|
||||||
<template #prefix
|
|
||||||
>{{ url }}/@{{ author.username }}/pages/</template
|
|
||||||
>
|
|
||||||
<template #label>{{ i18n.ts._pages.url }}</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkSwitch v-model="isPublic" class="_formBlock">{{
|
|
||||||
i18n.ts.public
|
|
||||||
}}</MkSwitch>
|
|
||||||
<MkSwitch v-model="alignCenter" class="_formBlock">{{
|
|
||||||
i18n.ts._pages.alignCenter
|
|
||||||
}}</MkSwitch>
|
|
||||||
|
|
||||||
<MkSelect v-model="font" class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts._pages.font }}</template>
|
|
||||||
<option value="serif">
|
|
||||||
{{ i18n.ts._pages.fontSerif }}
|
|
||||||
</option>
|
|
||||||
<option value="sans-serif">
|
|
||||||
{{ i18n.ts._pages.fontSansSerif }}
|
|
||||||
</option>
|
|
||||||
</MkSelect>
|
|
||||||
|
|
||||||
<MkSwitch
|
|
||||||
v-model="hideTitleWhenPinned"
|
|
||||||
class="_formBlock"
|
|
||||||
>{{ i18n.ts._pages.hideTitleWhenPinned }}</MkSwitch
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="eyeCatch">
|
|
||||||
<MkButton
|
|
||||||
v-if="eyeCatchingImageId == null && !readonly"
|
|
||||||
@click="setEyeCatchingImage"
|
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.eyeCatchingImageSet }}</MkButton
|
|
||||||
>
|
|
||||||
<div v-else-if="eyeCatchingImage">
|
|
||||||
<img
|
|
||||||
:src="eyeCatchingImage.url"
|
|
||||||
:alt="eyeCatchingImage.name"
|
|
||||||
style="max-width: 100%"
|
|
||||||
/>
|
|
||||||
<MkButton
|
|
||||||
v-if="!readonly"
|
|
||||||
@click="removeEyeCatchingImage()"
|
|
||||||
><i class="ph-trash ph-bold ph-lg"></i>
|
|
||||||
{{
|
|
||||||
i18n.ts._pages.eyeCatchingImageRemove
|
|
||||||
}}</MkButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'contents'">
|
|
||||||
<div>
|
|
||||||
<XBlocks v-model="content" class="content" :hpml="hpml" />
|
|
||||||
<MkButton v-if="!readonly" @click="add()"
|
|
||||||
><i class="ph-plus ph-bold ph-lg"></i
|
|
||||||
></MkButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'variables'">
|
|
||||||
<div class="qmuvgica">
|
|
||||||
<XDraggable
|
|
||||||
v-show="variables.length > 0"
|
|
||||||
v-model="variables"
|
|
||||||
tag="div"
|
|
||||||
class="variables"
|
|
||||||
item-key="name"
|
|
||||||
handle=".drag-handle"
|
|
||||||
:group="{ name: 'variables' }"
|
|
||||||
animation="150"
|
|
||||||
swap-threshold="0.5"
|
|
||||||
>
|
|
||||||
<template #item="{ element }">
|
|
||||||
<XVariable
|
|
||||||
:model-value="element"
|
|
||||||
:removable="true"
|
|
||||||
:hpml="hpml"
|
|
||||||
:name="element.name"
|
|
||||||
:title="element.name"
|
|
||||||
:draggable="true"
|
|
||||||
@remove="() => removeVariable(element)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</XDraggable>
|
|
||||||
|
|
||||||
<MkButton
|
|
||||||
v-if="!readonly"
|
|
||||||
class="add"
|
|
||||||
@click="addVariable()"
|
|
||||||
><i class="ph-plus ph-bold ph-lg"></i
|
|
||||||
></MkButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="tab === 'script'">
|
|
||||||
<div>
|
|
||||||
<MkTextarea v-model="script" class="_code" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineAsyncComponent, computed, provide, watch } from "vue";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import XVariable from "./page-editor.script-block.vue";
|
|
||||||
import XBlocks from "./page-editor.blocks.vue";
|
|
||||||
import MkTextarea from "@/components/form/textarea.vue";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import MkSelect from "@/components/form/select.vue";
|
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
|
||||||
import { blockDefs } from "@/scripts/hpml/index";
|
|
||||||
import { HpmlTypeChecker } from "@/scripts/hpml/type-checker";
|
|
||||||
import { url } from "@/config";
|
|
||||||
import { collectPageVars } from "@/scripts/collect-page-vars";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { selectFile } from "@/scripts/select-file";
|
|
||||||
import { mainRouter } from "@/router";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
|
|
||||||
const XDraggable = defineAsyncComponent(() =>
|
|
||||||
import("vuedraggable").then((x) => x.default)
|
|
||||||
);
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
initPageId?: string;
|
|
||||||
initPageName?: string;
|
|
||||||
initUser?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let tab = $ref("settings");
|
|
||||||
let author = $ref($i);
|
|
||||||
let readonly = $ref(false);
|
|
||||||
let page = $ref(null);
|
|
||||||
let pageId = $ref(null);
|
|
||||||
let currentName = $ref(null);
|
|
||||||
let title = $ref("");
|
|
||||||
let summary = $ref(null);
|
|
||||||
let name = $ref(Date.now().toString());
|
|
||||||
let eyeCatchingImage = $ref(null);
|
|
||||||
let eyeCatchingImageId = $ref(null);
|
|
||||||
let font = $ref("sans-serif");
|
|
||||||
let content = $ref([]);
|
|
||||||
let alignCenter = $ref(false);
|
|
||||||
let isPublic = $ref(true);
|
|
||||||
let hideTitleWhenPinned = $ref(false);
|
|
||||||
let variables = $ref([]);
|
|
||||||
let hpml = $ref(null);
|
|
||||||
let script = $ref("");
|
|
||||||
|
|
||||||
provide("readonly", readonly);
|
|
||||||
provide("getScriptBlockList", getScriptBlockList);
|
|
||||||
provide("getPageBlockList", getPageBlockList);
|
|
||||||
|
|
||||||
watch($$(eyeCatchingImageId), async () => {
|
|
||||||
if (eyeCatchingImageId == null) {
|
|
||||||
eyeCatchingImage = null;
|
|
||||||
} else {
|
|
||||||
eyeCatchingImage = await os.api("drive/files/show", {
|
|
||||||
fileId: eyeCatchingImageId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getSaveOptions() {
|
|
||||||
return {
|
|
||||||
title: title.trim(),
|
|
||||||
name: name.trim(),
|
|
||||||
summary: summary,
|
|
||||||
font: font,
|
|
||||||
script: script,
|
|
||||||
hideTitleWhenPinned: hideTitleWhenPinned,
|
|
||||||
alignCenter: alignCenter,
|
|
||||||
isPublic: isPublic,
|
|
||||||
content: content,
|
|
||||||
variables: variables,
|
|
||||||
eyeCatchingImageId: eyeCatchingImageId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
const options = getSaveOptions();
|
|
||||||
|
|
||||||
const onError = (err) => {
|
|
||||||
if (err.id === "3d81ceae-475f-4600-b2a8-2bc116157532") {
|
|
||||||
if (err.info.param === "name") {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
title: i18n.ts._pages.invalidNameTitle,
|
|
||||||
text: i18n.ts._pages.invalidNameText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (err.code === "NAME_ALREADY_EXISTS") {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: i18n.ts._pages.nameAlreadyExists,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pageId) {
|
|
||||||
options.pageId = pageId;
|
|
||||||
os.api("pages/update", options)
|
|
||||||
.then((page) => {
|
|
||||||
currentName = name.trim();
|
|
||||||
os.alert({
|
|
||||||
type: "success",
|
|
||||||
text: i18n.ts._pages.updated,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(onError);
|
|
||||||
} else {
|
|
||||||
os.api("pages/create", options)
|
|
||||||
.then((created) => {
|
|
||||||
pageId = created.id;
|
|
||||||
currentName = name.trim();
|
|
||||||
os.alert({
|
|
||||||
type: "success",
|
|
||||||
text: i18n.ts._pages.created,
|
|
||||||
});
|
|
||||||
mainRouter.push(`/pages/edit/${pageId}`);
|
|
||||||
})
|
|
||||||
.catch(onError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function del() {
|
|
||||||
os.confirm({
|
|
||||||
type: "warning",
|
|
||||||
text: i18n.t("removeAreYouSure", { x: title.trim() }),
|
|
||||||
}).then(({ canceled }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
os.api("pages/delete", {
|
|
||||||
pageId: pageId,
|
|
||||||
}).then(() => {
|
|
||||||
os.alert({
|
|
||||||
type: "success",
|
|
||||||
text: i18n.ts._pages.deleted,
|
|
||||||
});
|
|
||||||
mainRouter.push("/pages");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function duplicate() {
|
|
||||||
title = title + " - copy";
|
|
||||||
name = name + "-copy";
|
|
||||||
os.api("pages/create", getSaveOptions()).then((created) => {
|
|
||||||
pageId = created.id;
|
|
||||||
currentName = name.trim();
|
|
||||||
os.alert({
|
|
||||||
type: "success",
|
|
||||||
text: i18n.ts._pages.created,
|
|
||||||
});
|
|
||||||
mainRouter.push(`/pages/edit/${pageId}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function add() {
|
|
||||||
const { canceled, result: type } = await os.select({
|
|
||||||
type: null,
|
|
||||||
title: i18n.ts._pages.chooseBlock,
|
|
||||||
groupedItems: getPageBlockList(),
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const id = uuid();
|
|
||||||
content.push({ id, type });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addVariable() {
|
|
||||||
let { canceled, result: name } = await os.inputText({
|
|
||||||
title: i18n.ts._pages.enterVariableName,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
name = name.trim();
|
|
||||||
|
|
||||||
if (hpml.isUsedName(name)) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: i18n.ts._pages.variableNameIsAlreadyUsed,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuid();
|
|
||||||
variables.push({ id, name, type: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeVariable(v) {
|
|
||||||
variables = variables.filter((x) => x.name !== v.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPageBlockList() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: i18n.ts._pages.contentBlocks,
|
|
||||||
items: [
|
|
||||||
{ value: "section", text: i18n.ts._pages.blocks.section },
|
|
||||||
{ value: "text", text: i18n.ts._pages.blocks.text },
|
|
||||||
{ value: "image", text: i18n.ts._pages.blocks.image },
|
|
||||||
{ value: "textarea", text: i18n.ts._pages.blocks.textarea },
|
|
||||||
{ value: "note", text: i18n.ts._pages.blocks.note },
|
|
||||||
{ value: "canvas", text: i18n.ts._pages.blocks.canvas },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n.ts._pages.inputBlocks,
|
|
||||||
items: [
|
|
||||||
{ value: "button", text: i18n.ts._pages.blocks.button },
|
|
||||||
{
|
|
||||||
value: "radioButton",
|
|
||||||
text: i18n.ts._pages.blocks.radioButton,
|
|
||||||
},
|
|
||||||
{ value: "textInput", text: i18n.ts._pages.blocks.textInput },
|
|
||||||
{
|
|
||||||
value: "textareaInput",
|
|
||||||
text: i18n.ts._pages.blocks.textareaInput,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "numberInput",
|
|
||||||
text: i18n.ts._pages.blocks.numberInput,
|
|
||||||
},
|
|
||||||
{ value: "switch", text: i18n.ts._pages.blocks.switch },
|
|
||||||
{ value: "counter", text: i18n.ts._pages.blocks.counter },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n.ts._pages.specialBlocks,
|
|
||||||
items: [
|
|
||||||
{ value: "if", text: i18n.ts._pages.blocks.if },
|
|
||||||
{ value: "post", text: i18n.ts._pages.blocks.post },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptBlockList(type: string = null) {
|
|
||||||
const list = [];
|
|
||||||
|
|
||||||
const blocks = blockDefs.filter(
|
|
||||||
(block) =>
|
|
||||||
type == null ||
|
|
||||||
block.out == null ||
|
|
||||||
block.out === type ||
|
|
||||||
typeof block.out === "number"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const block of blocks) {
|
|
||||||
const category = list.find((x) => x.category === block.category);
|
|
||||||
if (category) {
|
|
||||||
category.items.push({
|
|
||||||
value: block.type,
|
|
||||||
text: i18n.t(`_pages.script.blocks.${block.type}`),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
list.push({
|
|
||||||
category: block.category,
|
|
||||||
label: i18n.t(`_pages.script.categories.${block.category}`),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
value: block.type,
|
|
||||||
text: i18n.t(`_pages.script.blocks.${block.type}`),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userFns = variables.filter((x) => x.type === "fn");
|
|
||||||
if (userFns.length > 0) {
|
|
||||||
list.unshift({
|
|
||||||
label: i18n.t("_pages.script.categories.fn"),
|
|
||||||
items: userFns.map((v) => ({
|
|
||||||
value: "fn:" + v.name,
|
|
||||||
text: v.name,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEyeCatchingImage(img) {
|
|
||||||
selectFile(img.currentTarget ?? img.target, null).then((file) => {
|
|
||||||
eyeCatchingImageId = file.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeEyeCatchingImage() {
|
|
||||||
eyeCatchingImageId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
hpml = new HpmlTypeChecker();
|
|
||||||
|
|
||||||
watch(
|
|
||||||
$$(variables),
|
|
||||||
() => {
|
|
||||||
hpml.variables = variables;
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
$$(content),
|
|
||||||
() => {
|
|
||||||
hpml.pageVars = collectPageVars(content);
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (props.initPageId) {
|
|
||||||
page = await os.api("pages/show", {
|
|
||||||
pageId: props.initPageId,
|
|
||||||
});
|
|
||||||
} else if (props.initPageName && props.initUser) {
|
|
||||||
page = await os.api("pages/show", {
|
|
||||||
name: props.initPageName,
|
|
||||||
username: props.initUser,
|
|
||||||
});
|
|
||||||
readonly = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page) {
|
|
||||||
author = page.user;
|
|
||||||
pageId = page.id;
|
|
||||||
title = page.title;
|
|
||||||
name = page.name;
|
|
||||||
currentName = page.name;
|
|
||||||
summary = page.summary;
|
|
||||||
font = page.font;
|
|
||||||
script = page.script;
|
|
||||||
hideTitleWhenPinned = page.hideTitleWhenPinned;
|
|
||||||
alignCenter = page.alignCenter;
|
|
||||||
isPublic = page.isPublic;
|
|
||||||
content = page.content;
|
|
||||||
variables = page.variables;
|
|
||||||
eyeCatchingImageId = page.eyeCatchingImageId;
|
|
||||||
} else {
|
|
||||||
const id = uuid();
|
|
||||||
content = [
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
type: "text",
|
|
||||||
text: "",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => [
|
|
||||||
{
|
|
||||||
key: "settings",
|
|
||||||
title: i18n.ts._pages.pageSetting,
|
|
||||||
icon: "ph-gear-six ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "contents",
|
|
||||||
title: i18n.ts._pages.contents,
|
|
||||||
icon: "ph-sticker ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "variables",
|
|
||||||
title: i18n.ts._pages.variables,
|
|
||||||
icon: "ph-magic-wand ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "script",
|
|
||||||
title: i18n.ts.script,
|
|
||||||
icon: "ph-code ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
definePageMetadata(
|
|
||||||
computed(() => {
|
|
||||||
let title = i18n.ts._pages.newPage;
|
|
||||||
if (props.initPageId) {
|
|
||||||
title = i18n.ts._pages.editPage;
|
|
||||||
} else if (props.initPageName && props.initUser) {
|
|
||||||
title = i18n.ts._pages.readPage;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: title,
|
|
||||||
icon: "ph-pencil ph-bold ph-lg",
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.jqqmcavi {
|
|
||||||
> .button {
|
|
||||||
& + .button {
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gwbmwxkm {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
> .title {
|
|
||||||
z-index: 1;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 16px;
|
|
||||||
line-height: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 1px rgba(#000, 0.07);
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .buttons {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 0;
|
|
||||||
width: 42px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
line-height: 42px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> section {
|
|
||||||
padding: 0 32px 32px 32px;
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .view {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 16px 0 0 0;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .content {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .eyeCatch {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
> img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.qmuvgica {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .variables {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .add {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,469 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header
|
|
||||||
><MkPageHeader
|
|
||||||
:actions="headerActions"
|
|
||||||
:tabs="headerTabs"
|
|
||||||
:display-back-button="true"
|
|
||||||
/></template>
|
|
||||||
<MkSpacer :content-max="800">
|
|
||||||
<transition
|
|
||||||
:name="$store.state.animation ? 'fade' : ''"
|
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="page"
|
|
||||||
:key="page.id"
|
|
||||||
v-size="{ max: [450] }"
|
|
||||||
class="xcukqgmh"
|
|
||||||
>
|
|
||||||
<div class="footer">
|
|
||||||
<div>
|
|
||||||
<i class="ph-alarm ph-bold" />
|
|
||||||
{{ i18n.ts.createdAt }}:
|
|
||||||
<MkTime :time="page.createdAt" mode="detail" />
|
|
||||||
</div>
|
|
||||||
<div v-if="page.createdAt != page.updatedAt">
|
|
||||||
<i class="ph-alarm ph-bold"></i>
|
|
||||||
{{ i18n.ts.updatedAt }}:
|
|
||||||
<MkTime :time="page.updatedAt" mode="detail" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="_block main">
|
|
||||||
<div class="banner">
|
|
||||||
<div class="banner-image">
|
|
||||||
<div class="header">
|
|
||||||
<h1>{{ page.title }}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="menu-actions">
|
|
||||||
<button
|
|
||||||
v-tooltip="i18n.ts.copyUrl"
|
|
||||||
@click="copyUrl"
|
|
||||||
class="menu _button"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="ph-link-simple ph-bold ph-lg"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<MkA
|
|
||||||
v-tooltip="i18n.ts._pages.viewSource"
|
|
||||||
:to="`/@${username}/pages/${pageName}/view-source`"
|
|
||||||
class="menu _button"
|
|
||||||
style="transform: translateY(2px)"
|
|
||||||
><i class="ph-code ph-bold ph-lg"
|
|
||||||
/></MkA>
|
|
||||||
<template
|
|
||||||
v-if="$i && $i.id === page.userId"
|
|
||||||
>
|
|
||||||
<MkA
|
|
||||||
v-tooltip="i18n.ts._pages.editPage"
|
|
||||||
class="menu _button"
|
|
||||||
:to="`/pages/edit/${page.id}`"
|
|
||||||
style="transform: translateY(2px)"
|
|
||||||
><i class="ph-pencil ph-bold ph-lg"
|
|
||||||
/></MkA>
|
|
||||||
<button
|
|
||||||
v-if="$i.pinnedPageId === page.id"
|
|
||||||
v-tooltip="i18n.ts.unpin"
|
|
||||||
class="menu _button"
|
|
||||||
@click="pin(false)"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="ph-push-pin-slash ph-bold ph-lg"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
v-tooltip="i18n.ts.pin"
|
|
||||||
class="menu _button"
|
|
||||||
@click="pin(true)"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="ph-push-pin ph-bold ph-lg"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<XPage :page="page" />
|
|
||||||
</div>
|
|
||||||
<div class="actions">
|
|
||||||
<div class="like">
|
|
||||||
<MkButton
|
|
||||||
v-if="page.isLiked"
|
|
||||||
v-tooltip="i18n.ts._pages.unlike"
|
|
||||||
class="button"
|
|
||||||
primary
|
|
||||||
@click="unlike()"
|
|
||||||
><i class="ph-heart ph-fill ph-lg"></i
|
|
||||||
><span
|
|
||||||
v-if="page.likedCount > 0"
|
|
||||||
class="count"
|
|
||||||
>{{ page.likedCount }}</span
|
|
||||||
></MkButton
|
|
||||||
>
|
|
||||||
<MkButton
|
|
||||||
v-else
|
|
||||||
v-tooltip="i18n.ts._pages.like"
|
|
||||||
class="button"
|
|
||||||
@click="like()"
|
|
||||||
><i class="ph-heart ph-bold"></i
|
|
||||||
><span
|
|
||||||
v-if="page.likedCount > 0"
|
|
||||||
class="count"
|
|
||||||
>{{ page.likedCount }}</span
|
|
||||||
></MkButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="other">
|
|
||||||
<button
|
|
||||||
v-tooltip="i18n.ts.shareWithNote"
|
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
|
||||||
@click="shareWithNote"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="ph-repeat ph-bold ph-lg ph-fw ph-lg"
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="shareAvailable()"
|
|
||||||
v-tooltip="i18n.ts.share"
|
|
||||||
v-click-anime
|
|
||||||
class="_button"
|
|
||||||
@click="share"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="ph-share-network ph-bold ph-lg ph-fw ph-lg"
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="user">
|
|
||||||
<MagAvatarResolvingProxy
|
|
||||||
:user="page.user"
|
|
||||||
class="avatar"
|
|
||||||
/>
|
|
||||||
<div class="name">
|
|
||||||
<MkUserName
|
|
||||||
:user="page.user"
|
|
||||||
style="display: block"
|
|
||||||
/>
|
|
||||||
<MkAcct :user="page.user" />
|
|
||||||
</div>
|
|
||||||
<MkFollowButton
|
|
||||||
v-if="!$i || $i.id != page.user.id"
|
|
||||||
:user="page.user"
|
|
||||||
:inline="true"
|
|
||||||
:transparent="false"
|
|
||||||
:full="true"
|
|
||||||
class="koudoku"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="links">
|
|
||||||
<MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ i18n.ts._pages.viewSource }}</MkA>
|
|
||||||
<template v-if="$i && $i.id === page.userId">
|
|
||||||
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ i18n.ts._pages.editThisPage }}</MkA>
|
|
||||||
<button v-if="$i.pinnedPageId === page.id" class="link _textButton" @click="pin(false)">{{ i18n.ts.unpin }}</button>
|
|
||||||
<button v-else class="link _textButton" @click="pin(true)">{{ i18n.ts.pin }}</button>
|
|
||||||
</template>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
<MkContainer
|
|
||||||
:max-height="300"
|
|
||||||
:foldable="true"
|
|
||||||
:expanded="false"
|
|
||||||
class="other"
|
|
||||||
>
|
|
||||||
<template #header
|
|
||||||
><i class="ph-clock ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.recentPosts }}</template
|
|
||||||
>
|
|
||||||
<MkPagination
|
|
||||||
v-slot="{ items }"
|
|
||||||
:pagination="otherPostsPagination"
|
|
||||||
>
|
|
||||||
<MkPagePreview
|
|
||||||
v-for="page in items"
|
|
||||||
:key="page.id"
|
|
||||||
:page="page"
|
|
||||||
class="_gap"
|
|
||||||
/>
|
|
||||||
</MkPagination>
|
|
||||||
</MkContainer>
|
|
||||||
</div>
|
|
||||||
<MkError v-else-if="error" @retry="fetchPage()" />
|
|
||||||
<MkLoading v-else />
|
|
||||||
</transition>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch } from "vue";
|
|
||||||
import XPage from "@/components/page/page.vue";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { url } from "@/config";
|
|
||||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import MkPagePreview from "@/components/MkPagePreview.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
import { shareAvailable } from "@/scripts/share-available";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
pageName: string;
|
|
||||||
username: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let page = $ref(null);
|
|
||||||
let bgImg = $ref(null);
|
|
||||||
let error = $ref(null);
|
|
||||||
const otherPostsPagination = {
|
|
||||||
endpoint: "users/pages" as const,
|
|
||||||
limit: 6,
|
|
||||||
params: computed(() => ({
|
|
||||||
userId: page.user.id,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
const path = $computed(() => props.username + "/" + props.pageName);
|
|
||||||
|
|
||||||
function fetchPage() {
|
|
||||||
page = null;
|
|
||||||
os.api("pages/show", {
|
|
||||||
name: props.pageName,
|
|
||||||
username: props.username,
|
|
||||||
})
|
|
||||||
.then((_page) => {
|
|
||||||
page = _page;
|
|
||||||
bgImg = getBgImg();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
error = err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyUrl() {
|
|
||||||
copyToClipboard(window.location.href);
|
|
||||||
os.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBgImg(): string {
|
|
||||||
if (page.eyeCatchingImage != null) {
|
|
||||||
return `url(${page.eyeCatchingImage.url})`;
|
|
||||||
} else {
|
|
||||||
return "linear-gradient(to bottom right, #31748f, #9ccfd8)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function share() {
|
|
||||||
navigator.share({
|
|
||||||
title: page.title ?? page.name,
|
|
||||||
text: page.summary,
|
|
||||||
url: `${url}/@${page.user.username}/pages/${page.name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shareWithNote() {
|
|
||||||
os.post({
|
|
||||||
initialText: `${page.title || page.name} ${url}/@${
|
|
||||||
page.user.username
|
|
||||||
}/pages/${page.name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function like() {
|
|
||||||
os.api("pages/like", {
|
|
||||||
pageId: page.id,
|
|
||||||
}).then(() => {
|
|
||||||
page.isLiked = true;
|
|
||||||
page.likedCount++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unlike() {
|
|
||||||
os.api("pages/unlike", {
|
|
||||||
pageId: page.id,
|
|
||||||
}).then(() => {
|
|
||||||
page.isLiked = false;
|
|
||||||
page.likedCount--;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pin(pin) {
|
|
||||||
os.apiWithDialog("i/update", {
|
|
||||||
pinnedPageId: pin ? page.id : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => path, fetchPage, { immediate: true });
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
|
||||||
|
|
||||||
definePageMetadata(
|
|
||||||
computed(() =>
|
|
||||||
page
|
|
||||||
? {
|
|
||||||
title: computed(() => page.title || page.name),
|
|
||||||
avatar: page.user,
|
|
||||||
path: `/@${page.user.username}/pages/${page.name}`,
|
|
||||||
share: {
|
|
||||||
title: page.title || page.name,
|
|
||||||
text: page.summary,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.125s ease;
|
|
||||||
}
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xcukqgmh {
|
|
||||||
> .main {
|
|
||||||
> * {
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .banner {
|
|
||||||
margin: 0rem !important;
|
|
||||||
|
|
||||||
> .banner-image {
|
|
||||||
// TODO: 良い感じのアスペクト比で表示
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 150px;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: v-bind("bgImg");
|
|
||||||
|
|
||||||
> .header {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> h1 {
|
|
||||||
margin: 0;
|
|
||||||
color: white;
|
|
||||||
text-shadow: 0 0 8px var(--shadow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .menu-actions {
|
|
||||||
-webkit-backdrop-filter: var(--blur, blur(8px));
|
|
||||||
backdrop-filter: var(--blur, blur(8px));
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 24px;
|
|
||||||
width: fit-content;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
left: 1rem;
|
|
||||||
|
|
||||||
> .menu {
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 31px;
|
|
||||||
width: 31px;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 0 8px var(--shadow);
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .koudoku {
|
|
||||||
margin-left: 4px;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .content {
|
|
||||||
padding: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 16px 0;
|
|
||||||
border-top: solid 0.5px var(--divider);
|
|
||||||
|
|
||||||
> .like {
|
|
||||||
> .button {
|
|
||||||
--accent: #eb6f92;
|
|
||||||
--X8: #eb6f92;
|
|
||||||
--buttonBg: rgb(216 71 106 / 5%);
|
|
||||||
--buttonHoverBg: rgb(216 71 106 / 10%);
|
|
||||||
color: #eb6f92;
|
|
||||||
|
|
||||||
::v-deep(.count) {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .other {
|
|
||||||
> button {
|
|
||||||
padding: 2px;
|
|
||||||
margin: 0 8px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--fgHighlighted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .user {
|
|
||||||
margin-left: auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> .avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .name {
|
|
||||||
margin: 0 0 0 12px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .koudoku {
|
|
||||||
margin-left: auto;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .links {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 14px 0;
|
|
||||||
border-top: solid 0.5px var(--divider);
|
|
||||||
|
|
||||||
> .link {
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .footer {
|
|
||||||
margin: var(--margin) 0 var(--margin) 0;
|
|
||||||
font-size: 85%;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,196 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header
|
|
||||||
><MkPageHeader
|
|
||||||
v-model:tab="tab"
|
|
||||||
:actions="headerActions"
|
|
||||||
:tabs="headerTabs"
|
|
||||||
/></template>
|
|
||||||
<MkSpacer :content-max="700">
|
|
||||||
<swiper
|
|
||||||
:round-lengths="true"
|
|
||||||
:touch-angle="25"
|
|
||||||
:threshold="10"
|
|
||||||
:centeredSlides="true"
|
|
||||||
:modules="[Virtual]"
|
|
||||||
:space-between="20"
|
|
||||||
:virtual="true"
|
|
||||||
:allow-touch-move="
|
|
||||||
!(
|
|
||||||
deviceKind === 'desktop' &&
|
|
||||||
!defaultStore.state.swipeOnDesktop
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@swiper="setSwiperRef"
|
|
||||||
@slide-change="onSlideChange"
|
|
||||||
>
|
|
||||||
<swiper-slide>
|
|
||||||
<div class="rknalgpo">
|
|
||||||
<MkPagination
|
|
||||||
v-slot="{ items }"
|
|
||||||
:pagination="featuredPagesPagination"
|
|
||||||
>
|
|
||||||
<MkPagePreview
|
|
||||||
v-for="page in items"
|
|
||||||
:key="page.id"
|
|
||||||
class="ckltabjg"
|
|
||||||
:page="page"
|
|
||||||
/>
|
|
||||||
</MkPagination>
|
|
||||||
</div>
|
|
||||||
</swiper-slide>
|
|
||||||
<swiper-slide>
|
|
||||||
<div class="rknalgpo liked">
|
|
||||||
<MkPagination
|
|
||||||
v-slot="{ items }"
|
|
||||||
:pagination="likedPagesPagination"
|
|
||||||
>
|
|
||||||
<MkPagePreview
|
|
||||||
v-for="like in items"
|
|
||||||
:key="like.page.id"
|
|
||||||
class="ckltabjg"
|
|
||||||
:page="like.page"
|
|
||||||
/>
|
|
||||||
</MkPagination>
|
|
||||||
</div>
|
|
||||||
</swiper-slide>
|
|
||||||
<swiper-slide>
|
|
||||||
<div class="rknalgpo my">
|
|
||||||
<div class="buttoncontainer">
|
|
||||||
<MkButton class="new primary" @click="create()"
|
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts._pages.newPage }}</MkButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<MkPagination
|
|
||||||
v-slot="{ items }"
|
|
||||||
:pagination="myPagesPagination"
|
|
||||||
>
|
|
||||||
<MkPagePreview
|
|
||||||
v-for="page in items"
|
|
||||||
:key="page.id"
|
|
||||||
class="ckltabjg"
|
|
||||||
:page="page"
|
|
||||||
/>
|
|
||||||
</MkPagination>
|
|
||||||
</div>
|
|
||||||
</swiper-slide>
|
|
||||||
</swiper>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch, onMounted } from "vue";
|
|
||||||
import { Virtual } from "swiper";
|
|
||||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
|
||||||
import MkPagePreview from "@/components/MkPagePreview.vue";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import { useRouter } from "@/router";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import "swiper/scss";
|
|
||||||
import "swiper/scss/virtual";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
let tab = $ref("featured");
|
|
||||||
const tabs = ["featured", "liked", "my"];
|
|
||||||
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
|
|
||||||
|
|
||||||
const featuredPagesPagination = {
|
|
||||||
endpoint: "pages/featured" as const,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
const likedPagesPagination = {
|
|
||||||
endpoint: "i/page-likes" as const,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
const myPagesPagination = {
|
|
||||||
endpoint: "i/pages" as const,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
function create() {
|
|
||||||
router.push("/pages/new");
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = $computed(() => [
|
|
||||||
{
|
|
||||||
icon: "ph-plus ph-bold ph-lg",
|
|
||||||
text: i18n.ts.create,
|
|
||||||
handler: create,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => [
|
|
||||||
{
|
|
||||||
key: "featured",
|
|
||||||
title: i18n.ts._pages.featured,
|
|
||||||
icon: "ph-fire-simple ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "liked",
|
|
||||||
title: i18n.ts._pages.liked,
|
|
||||||
icon: "ph-heart ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "my",
|
|
||||||
title: i18n.ts._pages.my,
|
|
||||||
icon: "ph-crown-simple ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
definePageMetadata(
|
|
||||||
computed(() => ({
|
|
||||||
title: i18n.ts.pages,
|
|
||||||
icon: "ph-file-text ph-bold ph-lg",
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
let swiperRef = null;
|
|
||||||
|
|
||||||
function setSwiperRef(swiper) {
|
|
||||||
swiperRef = swiper;
|
|
||||||
syncSlide(tabs.indexOf(tab));
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSlideChange() {
|
|
||||||
tab = tabs[swiperRef.activeIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncSlide(index) {
|
|
||||||
swiperRef.slideTo(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
syncSlide(tabs.indexOf(swiperRef.activeIndex));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.rknalgpo {
|
|
||||||
> .buttoncontainer {
|
|
||||||
display: grid;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.my .ckltabjg:first-child {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckltabjg:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 500px) {
|
|
||||||
.ckltabjg:not(:last-child) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,164 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="iltifgqe">
|
|
||||||
<div class="editor _panel _gap">
|
|
||||||
<PrismEditor
|
|
||||||
v-model="code"
|
|
||||||
class="_code code"
|
|
||||||
style="height: 30vh"
|
|
||||||
:highlight="highlighter"
|
|
||||||
:line-numbers="false"
|
|
||||||
/>
|
|
||||||
<MkButton
|
|
||||||
style="position: absolute; top: 8px; right: 8px"
|
|
||||||
primary
|
|
||||||
@click="run()"
|
|
||||||
><i class="ph-play ph-bold ph-lg"></i
|
|
||||||
></MkButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkContainer :foldable="true" class="_gap">
|
|
||||||
<template #header>{{ i18n.ts.output }}</template>
|
|
||||||
<div class="bepmlvbi">
|
|
||||||
<div
|
|
||||||
v-for="log in logs"
|
|
||||||
:key="log.id"
|
|
||||||
class="log"
|
|
||||||
:class="{ print: log.print }"
|
|
||||||
>
|
|
||||||
{{ log.text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkContainer>
|
|
||||||
|
|
||||||
<div class="_gap">
|
|
||||||
{{ i18n.ts.scratchpadDescription }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
import "prismjs";
|
|
||||||
import { highlight, languages } from "prismjs/components/prism-core";
|
|
||||||
import "prismjs/components/prism-clike";
|
|
||||||
import "prismjs/components/prism-javascript";
|
|
||||||
import "prismjs/themes/prism-okaidia.css";
|
|
||||||
import { PrismEditor } from "vue-prism-editor";
|
|
||||||
import "vue-prism-editor/dist/prismeditor.min.css";
|
|
||||||
import { AiScript, parse, utils } from "@syuilo/aiscript";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import { createAiScriptEnv } from "@/scripts/aiscript/api";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
|
|
||||||
const code = ref("");
|
|
||||||
const logs = ref<any[]>([]);
|
|
||||||
|
|
||||||
const saved = localStorage.getItem("scratchpad");
|
|
||||||
if (saved) {
|
|
||||||
code.value = saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(code, () => {
|
|
||||||
localStorage.setItem("scratchpad", code.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
logs.value = [];
|
|
||||||
const aiscript = new AiScript(
|
|
||||||
createAiScriptEnv({
|
|
||||||
storageKey: "scratchpad",
|
|
||||||
token: $i?.token,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
in: (q) => {
|
|
||||||
return new Promise((ok) => {
|
|
||||||
os.inputText({
|
|
||||||
title: q,
|
|
||||||
}).then(({ canceled, result: a }) => {
|
|
||||||
ok(a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
out: (value) => {
|
|
||||||
logs.value.push({
|
|
||||||
id: Math.random(),
|
|
||||||
text:
|
|
||||||
value.type === "str"
|
|
||||||
? value.value
|
|
||||||
: utils.valToString(value),
|
|
||||||
print: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
log: (type, params) => {
|
|
||||||
switch (type) {
|
|
||||||
case "end":
|
|
||||||
logs.value.push({
|
|
||||||
id: Math.random(),
|
|
||||||
text: utils.valToString(params.val, true),
|
|
||||||
print: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(code.value);
|
|
||||||
} catch (error) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "Syntax error :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await aiscript.exec(ast);
|
|
||||||
} catch (error: any) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlighter(code) {
|
|
||||||
return highlight(code, languages.js, "javascript");
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
|
||||||
|
|
||||||
definePageMetadata({
|
|
||||||
title: i18n.ts.scratchpad,
|
|
||||||
icon: "ph-terminal-window ph-bold ph-lg",
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.iltifgqe {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .editor {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bepmlvbi {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .log {
|
|
||||||
&:not(.print) {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -6,12 +6,6 @@
|
||||||
<FormLink to="/settings/apps" class="_formBlock">{{
|
<FormLink to="/settings/apps" class="_formBlock">{{
|
||||||
i18n.ts.manageAccessTokens
|
i18n.ts.manageAccessTokens
|
||||||
}}</FormLink>
|
}}</FormLink>
|
||||||
<FormLink
|
|
||||||
to="/api-console"
|
|
||||||
:behavior="isDesktop ? 'window' : null"
|
|
||||||
class="_formBlock"
|
|
||||||
>API console</FormLink
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -157,12 +157,6 @@ const menuDef = computed(() => [
|
||||||
to: "/settings/sounds",
|
to: "/settings/sounds",
|
||||||
active: currentPage?.route.name === "sounds",
|
active: currentPage?.route.name === "sounds",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: "ph-plug ph-bold ph-lg",
|
|
||||||
text: i18n.ts.plugins,
|
|
||||||
to: "/settings/plugin",
|
|
||||||
active: currentPage?.route.name === "plugin",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="_formRoot">
|
|
||||||
<FormInfo warn class="_formBlock">{{
|
|
||||||
i18n.ts._plugin.installWarn
|
|
||||||
}}</FormInfo>
|
|
||||||
|
|
||||||
<FormTextarea v-model="code" tall class="_formBlock">
|
|
||||||
<template #label>{{ i18n.ts.code }}</template>
|
|
||||||
</FormTextarea>
|
|
||||||
|
|
||||||
<div class="_formBlock">
|
|
||||||
<FormButton :disabled="code == null" primary inline @click="install"
|
|
||||||
><i class="ph-check ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.install }}</FormButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineAsyncComponent, nextTick, ref } from "vue";
|
|
||||||
import { AiScript, parse } from "@syuilo/aiscript";
|
|
||||||
import { serialize } from "@syuilo/aiscript/built/serializer";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import FormTextarea from "@/components/form/textarea.vue";
|
|
||||||
import FormButton from "@/components/MkButton.vue";
|
|
||||||
import FormInfo from "@/components/MkInfo.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { ColdDeviceStorage } from "@/store";
|
|
||||||
import { unisonReload } from "@/scripts/unison-reload";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
|
|
||||||
const code = ref(null);
|
|
||||||
|
|
||||||
function installPlugin({ id, meta, ast, token }) {
|
|
||||||
ColdDeviceStorage.set(
|
|
||||||
"plugins",
|
|
||||||
ColdDeviceStorage.get("plugins").concat({
|
|
||||||
...meta,
|
|
||||||
id,
|
|
||||||
active: true,
|
|
||||||
configData: {},
|
|
||||||
token: token,
|
|
||||||
ast: ast,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function install() {
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(code.value);
|
|
||||||
} catch (err) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "Syntax error :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = AiScript.collectMetadata(ast);
|
|
||||||
if (meta == null) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "No metadata found :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = meta.get(null);
|
|
||||||
if (metadata == null) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "No metadata found :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, version, author, description, permissions, config } =
|
|
||||||
metadata;
|
|
||||||
if (name == null || version == null || author == null) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "Required property not found :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token =
|
|
||||||
permissions == null || permissions.length === 0
|
|
||||||
? null
|
|
||||||
: await new Promise((res, rej) => {
|
|
||||||
os.popup(
|
|
||||||
defineAsyncComponent(
|
|
||||||
() => import("@/components/MkTokenGenerateWindow.vue")
|
|
||||||
),
|
|
||||||
{
|
|
||||||
title: i18n.ts.tokenRequested,
|
|
||||||
information: i18n.ts.pluginTokenRequestedDescription,
|
|
||||||
initialName: name,
|
|
||||||
initialPermissions: permissions,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
done: async (result) => {
|
|
||||||
const { name, permissions } = result;
|
|
||||||
const { token } = await os.api(
|
|
||||||
"miauth/gen-token",
|
|
||||||
{
|
|
||||||
session: null,
|
|
||||||
name: name,
|
|
||||||
permission: permissions,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
res(token);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"closed"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
installPlugin({
|
|
||||||
id: uuid(),
|
|
||||||
meta: {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
author,
|
|
||||||
description,
|
|
||||||
permissions,
|
|
||||||
config,
|
|
||||||
},
|
|
||||||
token,
|
|
||||||
ast: serialize(ast),
|
|
||||||
});
|
|
||||||
|
|
||||||
os.success();
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
unisonReload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
|
||||||
|
|
||||||
definePageMetadata({
|
|
||||||
title: i18n.ts._plugin.install,
|
|
||||||
icon: "ph-download-simple ph-bold ph-lg",
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,127 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="_formRoot">
|
|
||||||
<FormLink to="/settings/plugin/install"
|
|
||||||
><template #icon
|
|
||||||
><i class="ph-download-simple ph-bold ph-lg"></i></template
|
|
||||||
>{{ i18n.ts._plugin.install }}</FormLink
|
|
||||||
>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<template #label>{{ i18n.ts.manage }}</template>
|
|
||||||
<div
|
|
||||||
v-for="plugin in plugins"
|
|
||||||
:key="plugin.id"
|
|
||||||
class="_formBlock _panel"
|
|
||||||
style="padding: 20px"
|
|
||||||
>
|
|
||||||
<span style="display: flex"
|
|
||||||
><b>{{ plugin.name }}</b
|
|
||||||
><span style="margin-left: auto"
|
|
||||||
>v{{ plugin.version }}</span
|
|
||||||
></span
|
|
||||||
>
|
|
||||||
|
|
||||||
<FormSwitch
|
|
||||||
class="_formBlock"
|
|
||||||
:model-value="plugin.active"
|
|
||||||
@update:modelValue="changeActive(plugin, $event)"
|
|
||||||
>{{ i18n.ts.makeActive }}</FormSwitch
|
|
||||||
>
|
|
||||||
|
|
||||||
<MkKeyValue class="_formBlock">
|
|
||||||
<template #key>{{ i18n.ts.author }}</template>
|
|
||||||
<template #value>{{ plugin.author }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue class="_formBlock">
|
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
|
||||||
<template #value>{{ plugin.description }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue class="_formBlock">
|
|
||||||
<template #key>{{ i18n.ts.permission }}</template>
|
|
||||||
<template #value>{{ plugin.permission }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
|
|
||||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap">
|
|
||||||
<MkButton
|
|
||||||
v-if="plugin.config"
|
|
||||||
inline
|
|
||||||
@click="config(plugin)"
|
|
||||||
><i class="ph-gear-six ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.settings }}</MkButton
|
|
||||||
>
|
|
||||||
<MkButton inline danger @click="uninstall(plugin)"
|
|
||||||
><i class="ph-trash ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.uninstall }}</MkButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { nextTick, ref } from "vue";
|
|
||||||
import FormLink from "@/components/form/link.vue";
|
|
||||||
import FormSwitch from "@/components/form/switch.vue";
|
|
||||||
import FormSection from "@/components/form/section.vue";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import MkKeyValue from "@/components/MkKeyValue.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { ColdDeviceStorage } from "@/store";
|
|
||||||
import { unisonReload } from "@/scripts/unison-reload";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
|
||||||
|
|
||||||
const plugins = ref(ColdDeviceStorage.get("plugins"));
|
|
||||||
|
|
||||||
function uninstall(plugin) {
|
|
||||||
ColdDeviceStorage.set(
|
|
||||||
"plugins",
|
|
||||||
plugins.value.filter((x) => x.id !== plugin.id)
|
|
||||||
);
|
|
||||||
os.success();
|
|
||||||
nextTick(() => {
|
|
||||||
unisonReload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
|
|
||||||
async function config(plugin) {
|
|
||||||
const config = plugin.config;
|
|
||||||
for (const key in plugin.configData) {
|
|
||||||
config[key].default = plugin.configData[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { canceled, result } = await os.form(plugin.name, config);
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const coldPlugins = ColdDeviceStorage.get("plugins");
|
|
||||||
coldPlugins.find((p) => p.id === plugin.id)!.configData = result;
|
|
||||||
ColdDeviceStorage.set("plugins", coldPlugins);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeActive(plugin, active) {
|
|
||||||
const coldPlugins = ColdDeviceStorage.get("plugins");
|
|
||||||
coldPlugins.find((p) => p.id === plugin.id)!.active = active;
|
|
||||||
ColdDeviceStorage.set("plugins", coldPlugins);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
|
||||||
|
|
||||||
definePageMetadata({
|
|
||||||
title: i18n.ts.plugins,
|
|
||||||
icon: "ph-plug ph-bold ph-lg",
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -121,7 +121,6 @@ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||||
"lightTheme",
|
"lightTheme",
|
||||||
"darkTheme",
|
"darkTheme",
|
||||||
"syncDeviceDarkMode",
|
"syncDeviceDarkMode",
|
||||||
"plugins",
|
|
||||||
"mediaVolume",
|
"mediaVolume",
|
||||||
"sound_masterVolume",
|
"sound_masterVolume",
|
||||||
"sound_note",
|
"sound_note",
|
||||||
|
|
|
@ -308,27 +308,15 @@
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<MkA
|
<MkA :to="userPage(user)">
|
||||||
v-click-anime
|
|
||||||
:to="userPage(user)"
|
|
||||||
:class="{ active: page === 'index' }"
|
|
||||||
>
|
|
||||||
<b>{{ number(user.note_count) }}</b>
|
<b>{{ number(user.note_count) }}</b>
|
||||||
<span>{{ i18n.ts.notes }}</span>
|
<span>{{ i18n.ts.notes }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA :to="userPage(user, 'following')">
|
||||||
v-click-anime
|
|
||||||
:to="userPage(user, 'following')"
|
|
||||||
:class="{ active: page === 'following' }"
|
|
||||||
>
|
|
||||||
<b>{{ number(user.following_count) }}</b>
|
<b>{{ number(user.following_count) }}</b>
|
||||||
<span>{{ i18n.ts.following }}</span>
|
<span>{{ i18n.ts.following }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA :to="userPage(user, 'followers')">
|
||||||
v-click-anime
|
|
||||||
:to="userPage(user, 'followers')"
|
|
||||||
:class="{ active: page === 'followers' }"
|
|
||||||
>
|
|
||||||
<b>{{ number(user.follower_count) }}</b>
|
<b>{{ number(user.follower_count) }}</b>
|
||||||
<span>{{ i18n.ts.followers }}</span>
|
<span>{{ i18n.ts.followers }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
|
@ -395,7 +383,6 @@ import MkUserName from "@/components/global/MkUserName.vue";
|
||||||
import Mfm from "@/components/mfm.vue";
|
import Mfm from "@/components/mfm.vue";
|
||||||
import MkTime from "@/components/global/MkTime.vue";
|
import MkTime from "@/components/global/MkTime.vue";
|
||||||
import MkA from "@/components/global/MkA.vue";
|
import MkA from "@/components/global/MkA.vue";
|
||||||
import page from "@/components/page/page.vue";
|
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import number from "../../filters/number";
|
import number from "../../filters/number";
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
/>
|
/>
|
||||||
<XReactions v-else-if="tab === 'reactions'" :user="user" />
|
<XReactions v-else-if="tab === 'reactions'" :user="user" />
|
||||||
<XClips v-else-if="tab === 'clips'" :user="user" />
|
<XClips v-else-if="tab === 'clips'" :user="user" />
|
||||||
<XPages v-else-if="tab === 'pages'" :user="user" />
|
|
||||||
<XGallery v-else-if="tab === 'gallery'" :user="user" />
|
<XGallery v-else-if="tab === 'gallery'" :user="user" />
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchUser()" />
|
<MkError v-else-if="error" @retry="fetchUser()" />
|
||||||
|
@ -42,7 +41,6 @@ import * as Acct from "calckey-js/built/acct";
|
||||||
const XHome = defineAsyncComponent(() => import("./home.vue"));
|
const XHome = defineAsyncComponent(() => import("./home.vue"));
|
||||||
const XReactions = defineAsyncComponent(() => import("./reactions.vue"));
|
const XReactions = defineAsyncComponent(() => import("./reactions.vue"));
|
||||||
const XClips = defineAsyncComponent(() => import("./clips.vue"));
|
const XClips = defineAsyncComponent(() => import("./clips.vue"));
|
||||||
const XPages = defineAsyncComponent(() => import("./pages.vue"));
|
|
||||||
const XGallery = defineAsyncComponent(() => import("./gallery.vue"));
|
const XGallery = defineAsyncComponent(() => import("./gallery.vue"));
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -144,11 +142,6 @@ const headerTabs = $computed(() =>
|
||||||
title: i18n.ts.clips,
|
title: i18n.ts.clips,
|
||||||
icon: "ph-paperclip ph-bold ph-lg",
|
icon: "ph-paperclip ph-bold ph-lg",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "pages",
|
|
||||||
title: i18n.ts.pages,
|
|
||||||
icon: "ph-file-text ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "gallery",
|
key: "gallery",
|
||||||
title: i18n.ts.gallery,
|
title: i18n.ts.gallery,
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkSpacer :content-max="800">
|
|
||||||
<MkPagination v-slot="{ items }" ref="list" :pagination="pagination">
|
|
||||||
<MkPagePreview
|
|
||||||
v-for="page in items"
|
|
||||||
:key="page.id"
|
|
||||||
:page="page"
|
|
||||||
class="_gap"
|
|
||||||
/>
|
|
||||||
</MkPagination>
|
|
||||||
</MkSpacer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from "vue";
|
|
||||||
import MkPagePreview from "@/components/MkPagePreview.vue";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import { packed } from "magnetar-common";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
user: packed.PackUserBase;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: "users/pages" as const,
|
|
||||||
limit: 20,
|
|
||||||
params: computed(() => ({
|
|
||||||
userId: props.user.id,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -1,192 +0,0 @@
|
||||||
import { AiScript, utils, values } from "@syuilo/aiscript";
|
|
||||||
import { deserialize } from "@syuilo/aiscript/built/serializer";
|
|
||||||
import { jsToVal } from "@syuilo/aiscript/built/interpreter/util";
|
|
||||||
import { createAiScriptEnv } from "@/scripts/aiscript/api";
|
|
||||||
import { inputText } from "@/os";
|
|
||||||
import {
|
|
||||||
noteActions,
|
|
||||||
notePostInterruptors,
|
|
||||||
noteViewInterruptors,
|
|
||||||
postFormActions,
|
|
||||||
userActions,
|
|
||||||
} from "@/store";
|
|
||||||
|
|
||||||
const pluginContexts = new Map<string, AiScript>();
|
|
||||||
|
|
||||||
export function install(plugin) {
|
|
||||||
console.info("Plugin installed:", plugin.name, `v${plugin.version}`);
|
|
||||||
|
|
||||||
const aiscript = new AiScript(
|
|
||||||
createPluginEnv({
|
|
||||||
plugin: plugin,
|
|
||||||
storageKey: `plugins:${plugin.id}`,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
in: (q) => {
|
|
||||||
return new Promise((ok) => {
|
|
||||||
inputText({
|
|
||||||
title: q,
|
|
||||||
}).then(({ canceled, result: a }) => {
|
|
||||||
ok(a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
out: (value) => {
|
|
||||||
console.log(value);
|
|
||||||
},
|
|
||||||
log: (type, params) => {},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
initPlugin({ plugin, aiscript });
|
|
||||||
|
|
||||||
aiscript.exec(deserialize(plugin.ast));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPluginEnv(opts) {
|
|
||||||
const config = new Map();
|
|
||||||
for (const [k, v] of Object.entries(opts.plugin.config || {})) {
|
|
||||||
config.set(
|
|
||||||
k,
|
|
||||||
jsToVal(
|
|
||||||
typeof opts.plugin.configData[k] !== "undefined"
|
|
||||||
? opts.plugin.configData[k]
|
|
||||||
: v.default
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...createAiScriptEnv({ ...opts, token: opts.plugin.token }),
|
|
||||||
//#region Deprecated
|
|
||||||
"Mk:register_post_form_action": values.FN_NATIVE(([title, handler]) => {
|
|
||||||
registerPostFormAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
"Mk:register_user_action": values.FN_NATIVE(([title, handler]) => {
|
|
||||||
registerUserAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
"Mk:register_note_action": values.FN_NATIVE(([title, handler]) => {
|
|
||||||
registerNoteAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
//#endregion
|
|
||||||
"Plugin:register_post_form_action": values.FN_NATIVE(
|
|
||||||
([title, handler]) => {
|
|
||||||
registerPostFormAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"Plugin:register_user_action": values.FN_NATIVE(([title, handler]) => {
|
|
||||||
registerUserAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
"Plugin:register_note_action": values.FN_NATIVE(([title, handler]) => {
|
|
||||||
registerNoteAction({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
title: title.value,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
"Plugin:register_note_view_interruptor": values.FN_NATIVE(
|
|
||||||
([handler]) => {
|
|
||||||
registerNoteViewInterruptor({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"Plugin:register_note_post_interruptor": values.FN_NATIVE(
|
|
||||||
([handler]) => {
|
|
||||||
registerNotePostInterruptor({
|
|
||||||
pluginId: opts.plugin.id,
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"Plugin:open_url": values.FN_NATIVE(([url]) => {
|
|
||||||
window.open(url.value, "_blank");
|
|
||||||
}),
|
|
||||||
"Plugin:config": values.OBJ(config),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPlugin({ plugin, aiscript }) {
|
|
||||||
pluginContexts.set(plugin.id, aiscript);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerPostFormAction({ pluginId, title, handler }) {
|
|
||||||
postFormActions.push({
|
|
||||||
title,
|
|
||||||
handler: (form, update) => {
|
|
||||||
pluginContexts.get(pluginId)?.execFn(handler, [
|
|
||||||
utils.jsToVal(form),
|
|
||||||
values.FN_NATIVE(([key, value]) => {
|
|
||||||
update(key.value, value.value);
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerUserAction({ pluginId, title, handler }) {
|
|
||||||
userActions.push({
|
|
||||||
title,
|
|
||||||
handler: (user) => {
|
|
||||||
pluginContexts
|
|
||||||
.get(pluginId)
|
|
||||||
?.execFn(handler, [utils.jsToVal(user)]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerNoteAction({ pluginId, title, handler }) {
|
|
||||||
noteActions.push({
|
|
||||||
title,
|
|
||||||
handler: (note) => {
|
|
||||||
pluginContexts
|
|
||||||
.get(pluginId)
|
|
||||||
?.execFn(handler, [utils.jsToVal(note)]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerNoteViewInterruptor({ pluginId, handler }) {
|
|
||||||
noteViewInterruptors.push({
|
|
||||||
handler: async (note) => {
|
|
||||||
return utils.valToJs(
|
|
||||||
await pluginContexts
|
|
||||||
.get(pluginId)
|
|
||||||
.execFn(handler, [utils.jsToVal(note)])
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerNotePostInterruptor({ pluginId, handler }) {
|
|
||||||
notePostInterruptors.push({
|
|
||||||
handler: async (note) => {
|
|
||||||
return utils.valToJs(
|
|
||||||
await pluginContexts
|
|
||||||
.get(pluginId)
|
|
||||||
.execFn(handler, [utils.jsToVal(note)])
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -23,14 +23,6 @@ const page = (loader: AsyncComponentLoader<any>) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
{
|
|
||||||
path: "/@:initUser/pages/:initPageName/view-source",
|
|
||||||
component: page(() => import("./pages/page-editor/page-editor.vue")),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/@:username/pages/:pageName",
|
|
||||||
component: page(() => import("./pages/page.vue")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/@:acct/following",
|
path: "/@:acct/following",
|
||||||
component: page(() => import("./pages/user/following.vue")),
|
component: page(() => import("./pages/user/following.vue")),
|
||||||
|
@ -168,18 +160,6 @@ export const routes = [
|
||||||
name: "sounds",
|
name: "sounds",
|
||||||
component: page(() => import("./pages/settings/sounds.vue")),
|
component: page(() => import("./pages/settings/sounds.vue")),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/plugin/install",
|
|
||||||
name: "plugin",
|
|
||||||
component: page(
|
|
||||||
() => import("./pages/settings/plugin.install.vue")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/plugin",
|
|
||||||
name: "plugin",
|
|
||||||
component: page(() => import("./pages/settings/plugin.vue")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/import-export",
|
path: "/import-export",
|
||||||
name: "import-export",
|
name: "import-export",
|
||||||
|
@ -358,19 +338,10 @@ export const routes = [
|
||||||
component: page(() => import("./pages/share.vue")),
|
component: page(() => import("./pages/share.vue")),
|
||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/api-console",
|
|
||||||
component: page(() => import("./pages/api-console.vue")),
|
|
||||||
loginRequired: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/mfm-cheat-sheet",
|
path: "/mfm-cheat-sheet",
|
||||||
component: page(() => import("./pages/mfm-cheat-sheet.vue")),
|
component: page(() => import("./pages/mfm-cheat-sheet.vue")),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/scratchpad",
|
|
||||||
component: page(() => import("./pages/scratchpad.vue")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/preview",
|
path: "/preview",
|
||||||
component: page(() => import("./pages/preview.vue")),
|
component: page(() => import("./pages/preview.vue")),
|
||||||
|
@ -393,20 +364,6 @@ export const routes = [
|
||||||
path: "/tags/:tag",
|
path: "/tags/:tag",
|
||||||
component: page(() => import("./pages/tag.vue")),
|
component: page(() => import("./pages/tag.vue")),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/pages/new",
|
|
||||||
component: page(() => import("./pages/page-editor/page-editor.vue")),
|
|
||||||
loginRequired: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/pages/edit/:initPageId",
|
|
||||||
component: page(() => import("./pages/page-editor/page-editor.vue")),
|
|
||||||
loginRequired: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/pages",
|
|
||||||
component: page(() => import("./pages/pages.vue")),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/gallery/:postId/edit",
|
path: "/gallery/:postId/edit",
|
||||||
component: page(() => import("./pages/gallery/edit.vue")),
|
component: page(() => import("./pages/gallery/edit.vue")),
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { utils, values } from "@syuilo/aiscript";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
|
|
||||||
export function createAiScriptEnv(opts) {
|
|
||||||
let apiRequests = 0;
|
|
||||||
return {
|
|
||||||
USER_ID: $i ? values.STR($i.id) : values.NULL,
|
|
||||||
USER_NAME: $i ? values.STR($i.name) : values.NULL,
|
|
||||||
USER_USERNAME: $i ? values.STR($i.username) : values.NULL,
|
|
||||||
"Mk:dialog": values.FN_NATIVE(async ([title, text, type]) => {
|
|
||||||
await os.alert({
|
|
||||||
type: type ? type.value : "info",
|
|
||||||
title: title.value,
|
|
||||||
text: text.value,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
"Mk:confirm": values.FN_NATIVE(async ([title, text, type]) => {
|
|
||||||
const confirm = await os.confirm({
|
|
||||||
type: type ? type.value : "question",
|
|
||||||
title: title.value,
|
|
||||||
text: text.value,
|
|
||||||
});
|
|
||||||
return confirm.canceled ? values.FALSE : values.TRUE;
|
|
||||||
}),
|
|
||||||
"Mk:api": values.FN_NATIVE(async ([ep, param, token]) => {
|
|
||||||
if (token) {
|
|
||||||
utils.assertString(token);
|
|
||||||
// バグがあればundefinedもあり得るため念のため
|
|
||||||
if (typeof token.value !== "string")
|
|
||||||
throw new Error("invalid token");
|
|
||||||
}
|
|
||||||
apiRequests++;
|
|
||||||
if (apiRequests > 16) return values.NULL;
|
|
||||||
const res = await os.api(
|
|
||||||
ep.value,
|
|
||||||
utils.valToJs(param),
|
|
||||||
token ? token.value : opts.token || null
|
|
||||||
);
|
|
||||||
return utils.jsToVal(res);
|
|
||||||
}),
|
|
||||||
"Mk:save": values.FN_NATIVE(([key, value]) => {
|
|
||||||
utils.assertString(key);
|
|
||||||
localStorage.setItem(
|
|
||||||
`aiscript:${opts.storageKey}:${key.value}`,
|
|
||||||
JSON.stringify(utils.valToJs(value))
|
|
||||||
);
|
|
||||||
return values.NULL;
|
|
||||||
}),
|
|
||||||
"Mk:load": values.FN_NATIVE(([key]) => {
|
|
||||||
utils.assertString(key);
|
|
||||||
return utils.jsToVal(
|
|
||||||
JSON.parse(
|
|
||||||
localStorage.getItem(
|
|
||||||
`aiscript:${opts.storageKey}:${key.value}`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
export function collectPageVars(content) {
|
|
||||||
const pageVars = [];
|
|
||||||
const collect = (xs: any[]) => {
|
|
||||||
for (const x of xs) {
|
|
||||||
if (x.type === "textInput") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "string",
|
|
||||||
value: x.default || "",
|
|
||||||
});
|
|
||||||
} else if (x.type === "textareaInput") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "string",
|
|
||||||
value: x.default || "",
|
|
||||||
});
|
|
||||||
} else if (x.type === "numberInput") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "number",
|
|
||||||
value: x.default || 0,
|
|
||||||
});
|
|
||||||
} else if (x.type === "switch") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "boolean",
|
|
||||||
value: x.default,
|
|
||||||
});
|
|
||||||
} else if (x.type === "counter") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "number",
|
|
||||||
value: 0,
|
|
||||||
});
|
|
||||||
} else if (x.type === "radioButton") {
|
|
||||||
pageVars.push({
|
|
||||||
name: x.name,
|
|
||||||
type: "string",
|
|
||||||
value: x.default || "",
|
|
||||||
});
|
|
||||||
} else if (x.children) {
|
|
||||||
collect(x.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
collect(content);
|
|
||||||
return pageVars;
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import { instance } from "@/instance";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
import { noteActions } from "@/store";
|
|
||||||
import { shareAvailable } from "@/scripts/share-available";
|
import { shareAvailable } from "@/scripts/share-available";
|
||||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||||
import { magEffectiveNote, magTransUsername } from "@/scripts-mag/mag-util";
|
import { magEffectiveNote, magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
|
@ -487,18 +486,5 @@ export function getNoteMenu(props: {
|
||||||
].filter((x) => x !== undefined);
|
].filter((x) => x !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteActions.length > 0) {
|
|
||||||
menu = menu.concat([
|
|
||||||
null,
|
|
||||||
...noteActions.map((action) => ({
|
|
||||||
icon: "ph-plug ph-bold ph-lg",
|
|
||||||
text: action.title,
|
|
||||||
action: () => {
|
|
||||||
action.handler(appearNote);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { i18n } from "@/i18n";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { host } from "@/config";
|
import { host } from "@/config";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { userActions } from "@/store";
|
|
||||||
import { $i, iAmModerator } from "@/account";
|
import { $i, iAmModerator } from "@/account";
|
||||||
import { mainRouter } from "@/router";
|
import { mainRouter } from "@/router";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
|
@ -367,18 +366,5 @@ export function getUserMenu(
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userActions.length > 0) {
|
|
||||||
menu = menu.concat([
|
|
||||||
null,
|
|
||||||
...userActions.map((action) => ({
|
|
||||||
icon: "ph-plug ph-bold ph-lg",
|
|
||||||
text: action.title,
|
|
||||||
action: () => {
|
|
||||||
action.handler(user);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,14 +32,6 @@ export function openHelpMenu_(ev: MouseEvent) {
|
||||||
window.open(instance.tosUrl, "_blank");
|
window.open(instance.tosUrl, "_blank");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: i18n.ts.apps,
|
|
||||||
icon: "ph-device-mobile ph-bold ph-lg",
|
|
||||||
action: () => {
|
|
||||||
window.open("https://calckey.org/apps", "_blank");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
action: async () => {
|
action: async () => {
|
||||||
|
@ -55,25 +47,7 @@ export function openHelpMenu_(ev: MouseEvent) {
|
||||||
text: i18n.ts.developer,
|
text: i18n.ts.developer,
|
||||||
icon: "ph-code ph-bold ph-lg",
|
icon: "ph-code ph-bold ph-lg",
|
||||||
children: [
|
children: [
|
||||||
{
|
// TODO: Magnetar developer tools
|
||||||
type: "link",
|
|
||||||
to: "/api-console",
|
|
||||||
text: "API Console",
|
|
||||||
icon: "ph-terminal-window ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.ts.document,
|
|
||||||
icon: "ph-file-doc ph-bold ph-lg",
|
|
||||||
action: () => {
|
|
||||||
window.open("/api-doc", "_blank");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "link",
|
|
||||||
to: "/scratchpad",
|
|
||||||
text: "AiScript Scratchpad",
|
|
||||||
icon: "ph-scribble-loop ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
// blocks
|
|
||||||
|
|
||||||
export type BlockBase = {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TextBlock = BlockBase & {
|
|
||||||
type: "text";
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SectionBlock = BlockBase & {
|
|
||||||
type: "section";
|
|
||||||
title: string;
|
|
||||||
children: (Block | VarBlock)[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ImageBlock = BlockBase & {
|
|
||||||
type: "image";
|
|
||||||
fileId: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ButtonBlock = BlockBase & {
|
|
||||||
type: "button";
|
|
||||||
text: any;
|
|
||||||
primary: boolean;
|
|
||||||
action: string;
|
|
||||||
content: string;
|
|
||||||
event: string;
|
|
||||||
message: string;
|
|
||||||
var: string;
|
|
||||||
fn: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type IfBlock = BlockBase & {
|
|
||||||
type: "if";
|
|
||||||
var: string;
|
|
||||||
children: Block[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TextareaBlock = BlockBase & {
|
|
||||||
type: "textarea";
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PostBlock = BlockBase & {
|
|
||||||
type: "post";
|
|
||||||
text: string;
|
|
||||||
attachCanvasImage: boolean;
|
|
||||||
canvasId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CanvasBlock = BlockBase & {
|
|
||||||
type: "canvas";
|
|
||||||
name: string; // canvas id
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NoteBlock = BlockBase & {
|
|
||||||
type: "note";
|
|
||||||
detailed: boolean;
|
|
||||||
note: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Block =
|
|
||||||
| TextBlock
|
|
||||||
| SectionBlock
|
|
||||||
| ImageBlock
|
|
||||||
| ButtonBlock
|
|
||||||
| IfBlock
|
|
||||||
| TextareaBlock
|
|
||||||
| PostBlock
|
|
||||||
| CanvasBlock
|
|
||||||
| NoteBlock
|
|
||||||
| VarBlock;
|
|
||||||
|
|
||||||
// variable blocks
|
|
||||||
|
|
||||||
export type VarBlockBase = BlockBase & {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NumberInputVarBlock = VarBlockBase & {
|
|
||||||
type: "numberInput";
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TextInputVarBlock = VarBlockBase & {
|
|
||||||
type: "textInput";
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SwitchVarBlock = VarBlockBase & {
|
|
||||||
type: "switch";
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RadioButtonVarBlock = VarBlockBase & {
|
|
||||||
type: "radioButton";
|
|
||||||
title: string;
|
|
||||||
values: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CounterVarBlock = VarBlockBase & {
|
|
||||||
type: "counter";
|
|
||||||
text: string;
|
|
||||||
inc: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VarBlock =
|
|
||||||
| NumberInputVarBlock
|
|
||||||
| TextInputVarBlock
|
|
||||||
| SwitchVarBlock
|
|
||||||
| RadioButtonVarBlock
|
|
||||||
| CounterVarBlock;
|
|
||||||
|
|
||||||
const varBlock = [
|
|
||||||
"numberInput",
|
|
||||||
"textInput",
|
|
||||||
"switch",
|
|
||||||
"radioButton",
|
|
||||||
"counter",
|
|
||||||
];
|
|
||||||
export function isVarBlock(block: Block): block is VarBlock {
|
|
||||||
return varBlock.includes(block.type);
|
|
||||||
}
|
|
|
@ -1,264 +0,0 @@
|
||||||
import autobind from "autobind-decorator";
|
|
||||||
import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from ".";
|
|
||||||
import { version } from "@/config";
|
|
||||||
import { AiScript, utils, values } from "@syuilo/aiscript";
|
|
||||||
import { createAiScriptEnv } from "../aiscript/api";
|
|
||||||
import { collectPageVars } from "../collect-page-vars";
|
|
||||||
import { initHpmlLib, initAiLib } from "./lib";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { markRaw, ref, Ref, unref } from "vue";
|
|
||||||
import { Expr, isLiteralValue, Variable } from "./expr";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hpml evaluator
|
|
||||||
*/
|
|
||||||
export class Hpml {
|
|
||||||
private variables: Variable[];
|
|
||||||
private pageVars: PageVar[];
|
|
||||||
private envVars: Record<keyof typeof envVarsDef, any>;
|
|
||||||
public aiscript?: AiScript;
|
|
||||||
public pageVarUpdatedCallback?: values.VFn;
|
|
||||||
public canvases: Record<string, HTMLCanvasElement> = {};
|
|
||||||
public vars: Ref<Record<string, any>> = ref({});
|
|
||||||
public page: Record<string, any>;
|
|
||||||
|
|
||||||
private opts: {
|
|
||||||
randomSeed: string;
|
|
||||||
visitor?: any;
|
|
||||||
url?: string;
|
|
||||||
enableAiScript: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(page: Hpml["page"], opts: Hpml["opts"]) {
|
|
||||||
this.page = page;
|
|
||||||
this.variables = this.page.variables;
|
|
||||||
this.pageVars = collectPageVars(this.page.content);
|
|
||||||
this.opts = opts;
|
|
||||||
|
|
||||||
if (this.opts.enableAiScript) {
|
|
||||||
this.aiscript = markRaw(
|
|
||||||
new AiScript(
|
|
||||||
{
|
|
||||||
...createAiScriptEnv({
|
|
||||||
storageKey: `pages:${this.page.id}`,
|
|
||||||
}),
|
|
||||||
...initAiLib(this),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: (q) => {
|
|
||||||
return new Promise((ok) => {
|
|
||||||
os.inputText({
|
|
||||||
title: q,
|
|
||||||
}).then(({ canceled, result: a }) => {
|
|
||||||
ok(a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
out: (value) => {
|
|
||||||
console.log(value);
|
|
||||||
},
|
|
||||||
log: (type, params) => {},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.aiscript.scope.opts.onUpdated = (name, value) => {
|
|
||||||
this.eval();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
this.envVars = {
|
|
||||||
AI: "kawaii",
|
|
||||||
VERSION: version,
|
|
||||||
URL: this.page
|
|
||||||
? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}`
|
|
||||||
: "",
|
|
||||||
LOGIN: opts.visitor != null,
|
|
||||||
NAME: opts.visitor
|
|
||||||
? opts.visitor.name || opts.visitor.username
|
|
||||||
: "",
|
|
||||||
USERNAME: opts.visitor ? opts.visitor.username : "",
|
|
||||||
USERID: opts.visitor ? opts.visitor.id : "",
|
|
||||||
NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
|
|
||||||
FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
|
|
||||||
FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
|
|
||||||
IS_CAT: opts.visitor ? opts.visitor.isCat : false,
|
|
||||||
SEED: opts.randomSeed ? opts.randomSeed : "",
|
|
||||||
YMD: `${date.getFullYear()}/${
|
|
||||||
date.getMonth() + 1
|
|
||||||
}/${date.getDate()}`,
|
|
||||||
AISCRIPT_DISABLED: !this.opts.enableAiScript,
|
|
||||||
NULL: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.eval();
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public eval() {
|
|
||||||
try {
|
|
||||||
this.vars.value = this.evaluateVars();
|
|
||||||
} catch (err) {
|
|
||||||
//this.onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public interpolate(str: string) {
|
|
||||||
if (str == null) return null;
|
|
||||||
return str.replace(/{(.+?)}/g, (match) => {
|
|
||||||
const v = unref(this.vars)[match.slice(1, -1).trim()];
|
|
||||||
return v == null ? "NULL" : v.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public callAiScript(fn: string) {
|
|
||||||
try {
|
|
||||||
if (this.aiscript)
|
|
||||||
this.aiscript.execFn(this.aiscript.scope.get(fn), []);
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public registerCanvas(id: string, canvas: any) {
|
|
||||||
this.canvases[id] = canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public updatePageVar(name: string, value: any) {
|
|
||||||
const pageVar = this.pageVars.find((v) => v.name === name);
|
|
||||||
if (pageVar !== undefined) {
|
|
||||||
pageVar.value = value;
|
|
||||||
if (this.pageVarUpdatedCallback) {
|
|
||||||
if (this.aiscript)
|
|
||||||
this.aiscript.execFn(this.pageVarUpdatedCallback, [
|
|
||||||
values.STR(name),
|
|
||||||
utils.jsToVal(value),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new HpmlError(`No such page var '${name}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public updateRandomSeed(seed: string) {
|
|
||||||
this.opts.randomSeed = seed;
|
|
||||||
this.envVars.SEED = seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
private _interpolateScope(str: string, scope: HpmlScope) {
|
|
||||||
return str.replace(/{(.+?)}/g, (match) => {
|
|
||||||
const v = scope.getState(match.slice(1, -1).trim());
|
|
||||||
return v == null ? "NULL" : v.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public evaluateVars(): Record<string, any> {
|
|
||||||
const values: Record<string, any> = {};
|
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(this.envVars)) {
|
|
||||||
values[k] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const v of this.pageVars) {
|
|
||||||
values[v.name] = v.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const v of this.variables) {
|
|
||||||
values[v.name] = this.evaluate(v, new HpmlScope([values]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
private evaluate(expr: Expr, scope: HpmlScope): any {
|
|
||||||
if (isLiteralValue(expr)) {
|
|
||||||
if (expr.type === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.type === "number") {
|
|
||||||
return parseInt(expr.value as any, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.type === "text" || expr.type === "multiLineText") {
|
|
||||||
return this._interpolateScope(expr.value || "", scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.type === "textList") {
|
|
||||||
return this._interpolateScope(expr.value || "", scope)
|
|
||||||
.trim()
|
|
||||||
.split("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.type === "ref") {
|
|
||||||
return scope.getState(expr.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.type === "aiScriptVar") {
|
|
||||||
if (this.aiscript) {
|
|
||||||
try {
|
|
||||||
return utils.valToJs(
|
|
||||||
this.aiscript.scope.get(expr.value)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define user function
|
|
||||||
if (expr.type === "fn") {
|
|
||||||
return {
|
|
||||||
slots: expr.value.slots.map((x) => x.name),
|
|
||||||
exec: (slotArg: Record<string, any>) => {
|
|
||||||
return this.evaluate(
|
|
||||||
expr.value.expression,
|
|
||||||
scope.createChildScope(slotArg, expr.id)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} as Fn;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call user function
|
|
||||||
if (expr.type.startsWith("fn:")) {
|
|
||||||
const fnName = expr.type.split(":")[1];
|
|
||||||
const fn = scope.getState(fnName);
|
|
||||||
const args = {} as Record<string, any>;
|
|
||||||
for (let i = 0; i < fn.slots.length; i++) {
|
|
||||||
const name = fn.slots[i];
|
|
||||||
args[name] = this.evaluate(expr.args[i], scope);
|
|
||||||
}
|
|
||||||
return fn.exec(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.args === undefined) return null;
|
|
||||||
|
|
||||||
const funcs = initHpmlLib(
|
|
||||||
expr,
|
|
||||||
scope,
|
|
||||||
this.opts.randomSeed,
|
|
||||||
this.opts.visitor
|
|
||||||
);
|
|
||||||
|
|
||||||
// Call function
|
|
||||||
const fnName = expr.type;
|
|
||||||
const fn = (funcs as any)[fnName];
|
|
||||||
if (fn == null) {
|
|
||||||
throw new HpmlError(`No such function '${fnName}'`);
|
|
||||||
} else {
|
|
||||||
return fn(...expr.args.map((x) => this.evaluate(x, scope)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
import { literalDefs, Type } from ".";
|
|
||||||
|
|
||||||
export type ExprBase = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// value
|
|
||||||
|
|
||||||
export type EmptyValue = ExprBase & {
|
|
||||||
type: null;
|
|
||||||
value: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TextValue = ExprBase & {
|
|
||||||
type: "text";
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MultiLineTextValue = ExprBase & {
|
|
||||||
type: "multiLineText";
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TextListValue = ExprBase & {
|
|
||||||
type: "textList";
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NumberValue = ExprBase & {
|
|
||||||
type: "number";
|
|
||||||
value: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RefValue = ExprBase & {
|
|
||||||
type: "ref";
|
|
||||||
value: string; // value is variable name
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AiScriptRefValue = ExprBase & {
|
|
||||||
type: "aiScriptVar";
|
|
||||||
value: string; // value is variable name
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UserFnValue = ExprBase & {
|
|
||||||
type: "fn";
|
|
||||||
value: UserFnInnerValue;
|
|
||||||
};
|
|
||||||
type UserFnInnerValue = {
|
|
||||||
slots: {
|
|
||||||
name: string;
|
|
||||||
type: Type;
|
|
||||||
}[];
|
|
||||||
expression: Expr;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Value =
|
|
||||||
| EmptyValue
|
|
||||||
| TextValue
|
|
||||||
| MultiLineTextValue
|
|
||||||
| TextListValue
|
|
||||||
| NumberValue
|
|
||||||
| RefValue
|
|
||||||
| AiScriptRefValue
|
|
||||||
| UserFnValue;
|
|
||||||
|
|
||||||
export function isLiteralValue(expr: Expr): expr is Value {
|
|
||||||
if (expr.type == null) return true;
|
|
||||||
if (literalDefs[expr.type]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// call function
|
|
||||||
|
|
||||||
export type CallFn = ExprBase & {
|
|
||||||
// "fn:hoge" or string
|
|
||||||
type: string;
|
|
||||||
args: Expr[];
|
|
||||||
value: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// variable
|
|
||||||
export type Variable = (Value | CallFn) & {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// expression
|
|
||||||
export type Expr = Variable | Value | CallFn;
|
|
|
@ -1,140 +0,0 @@
|
||||||
/**
|
|
||||||
* Hpml
|
|
||||||
*/
|
|
||||||
|
|
||||||
import autobind from "autobind-decorator";
|
|
||||||
import { Hpml } from "./evaluator";
|
|
||||||
import { funcDefs } from "./lib";
|
|
||||||
|
|
||||||
export type Fn = {
|
|
||||||
slots: string[];
|
|
||||||
exec: (args: Record<string, any>) => ReturnType<Hpml["evaluate"]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Type = "string" | "number" | "boolean" | "stringArray" | null;
|
|
||||||
|
|
||||||
export const literalDefs: Record<
|
|
||||||
string,
|
|
||||||
{ out: any; category: string; icon: any }
|
|
||||||
> = {
|
|
||||||
text: { out: "string", category: "value", icon: "ph-quotes ph-bold ph-lg" },
|
|
||||||
multiLineText: {
|
|
||||||
out: "string",
|
|
||||||
category: "value",
|
|
||||||
icon: "ph-align-left ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
textList: {
|
|
||||||
out: "stringArray",
|
|
||||||
category: "value",
|
|
||||||
icon: "ph-list ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
out: "number",
|
|
||||||
category: "value",
|
|
||||||
icon: "ph-sort-descending-up ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
ref: { out: null, category: "value", icon: "ph-magic-wand ph-bold ph-lg" },
|
|
||||||
aiScriptVar: {
|
|
||||||
out: null,
|
|
||||||
category: "value",
|
|
||||||
icon: "ph-magic-wand ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
fn: {
|
|
||||||
out: "function",
|
|
||||||
category: "value",
|
|
||||||
icon: "ph-radical ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const blockDefs = [
|
|
||||||
...Object.entries(literalDefs).map(([k, v]) => ({
|
|
||||||
type: k,
|
|
||||||
out: v.out,
|
|
||||||
category: v.category,
|
|
||||||
icon: v.icon,
|
|
||||||
})),
|
|
||||||
...Object.entries(funcDefs).map(([k, v]) => ({
|
|
||||||
type: k,
|
|
||||||
out: v.out,
|
|
||||||
category: v.category,
|
|
||||||
icon: v.icon,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
export type PageVar = { name: string; value: any; type: Type };
|
|
||||||
|
|
||||||
export const envVarsDef: Record<string, Type> = {
|
|
||||||
AI: "string",
|
|
||||||
URL: "string",
|
|
||||||
VERSION: "string",
|
|
||||||
LOGIN: "boolean",
|
|
||||||
NAME: "string",
|
|
||||||
USERNAME: "string",
|
|
||||||
USERID: "string",
|
|
||||||
NOTES_COUNT: "number",
|
|
||||||
FOLLOWERS_COUNT: "number",
|
|
||||||
FOLLOWING_COUNT: "number",
|
|
||||||
IS_CAT: "boolean",
|
|
||||||
SEED: null,
|
|
||||||
YMD: "string",
|
|
||||||
AISCRIPT_DISABLED: "boolean",
|
|
||||||
NULL: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class HpmlScope {
|
|
||||||
private layerdStates: Record<string, any>[];
|
|
||||||
public name: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
layerdStates: HpmlScope["layerdStates"],
|
|
||||||
name?: HpmlScope["name"]
|
|
||||||
) {
|
|
||||||
this.layerdStates = layerdStates;
|
|
||||||
this.name = name || "anonymous";
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public createChildScope(
|
|
||||||
states: Record<string, any>,
|
|
||||||
name?: HpmlScope["name"]
|
|
||||||
): HpmlScope {
|
|
||||||
const layer = [states, ...this.layerdStates];
|
|
||||||
return new HpmlScope(layer, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指定した名前の変数の値を取得します
|
|
||||||
* @param name 変数名
|
|
||||||
*/
|
|
||||||
@autobind
|
|
||||||
public getState(name: string): any {
|
|
||||||
for (const later of this.layerdStates) {
|
|
||||||
const state = later[name];
|
|
||||||
if (state !== undefined) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new HpmlError(
|
|
||||||
`No such variable '${name}' in scope '${this.name}'`,
|
|
||||||
{
|
|
||||||
scope: this.layerdStates,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HpmlError extends Error {
|
|
||||||
public info?: any;
|
|
||||||
|
|
||||||
constructor(message: string, info?: any) {
|
|
||||||
super(message);
|
|
||||||
|
|
||||||
this.info = info;
|
|
||||||
|
|
||||||
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
Error.captureStackTrace(this, HpmlError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,587 +0,0 @@
|
||||||
import tinycolor from "tinycolor2";
|
|
||||||
import { Hpml } from "./evaluator";
|
|
||||||
import { values, utils } from "@syuilo/aiscript";
|
|
||||||
import { Fn, HpmlScope } from ".";
|
|
||||||
import { Expr } from "./expr";
|
|
||||||
import seedrandom from "seedrandom";
|
|
||||||
|
|
||||||
/* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
|
|
||||||
// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
|
|
||||||
Chart.pluginService.register({
|
|
||||||
beforeDraw: (chart, easing) => {
|
|
||||||
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
|
|
||||||
const ctx = chart.chart.ctx;
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
|
|
||||||
ctx.fillRect(0, 0, chart.chart.width, chart.chart.height);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initAiLib(hpml: Hpml) {
|
|
||||||
return {
|
|
||||||
"MkPages:updated": values.FN_NATIVE(([callback]) => {
|
|
||||||
hpml.pageVarUpdatedCallback = callback as values.VFn;
|
|
||||||
}),
|
|
||||||
"MkPages:get_canvas": values.FN_NATIVE(([id]) => {
|
|
||||||
utils.assertString(id);
|
|
||||||
const canvas = hpml.canvases[id.value];
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
return values.OBJ(
|
|
||||||
new Map([
|
|
||||||
[
|
|
||||||
"clear_rect",
|
|
||||||
values.FN_NATIVE(([x, y, width, height]) => {
|
|
||||||
ctx.clearRect(
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width.value,
|
|
||||||
height.value
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"fill_rect",
|
|
||||||
values.FN_NATIVE(([x, y, width, height]) => {
|
|
||||||
ctx.fillRect(
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width.value,
|
|
||||||
height.value
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"stroke_rect",
|
|
||||||
values.FN_NATIVE(([x, y, width, height]) => {
|
|
||||||
ctx.strokeRect(
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width.value,
|
|
||||||
height.value
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"fill_text",
|
|
||||||
values.FN_NATIVE(([text, x, y, width]) => {
|
|
||||||
ctx.fillText(
|
|
||||||
text.value,
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width ? width.value : undefined
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"stroke_text",
|
|
||||||
values.FN_NATIVE(([text, x, y, width]) => {
|
|
||||||
ctx.strokeText(
|
|
||||||
text.value,
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width ? width.value : undefined
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"set_line_width",
|
|
||||||
values.FN_NATIVE(([width]) => {
|
|
||||||
ctx.lineWidth = width.value;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"set_font",
|
|
||||||
values.FN_NATIVE(([font]) => {
|
|
||||||
ctx.font = font.value;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"set_fill_style",
|
|
||||||
values.FN_NATIVE(([style]) => {
|
|
||||||
ctx.fillStyle = style.value;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"set_stroke_style",
|
|
||||||
values.FN_NATIVE(([style]) => {
|
|
||||||
ctx.strokeStyle = style.value;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"begin_path",
|
|
||||||
values.FN_NATIVE(() => {
|
|
||||||
ctx.beginPath();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"close_path",
|
|
||||||
values.FN_NATIVE(() => {
|
|
||||||
ctx.closePath();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"move_to",
|
|
||||||
values.FN_NATIVE(([x, y]) => {
|
|
||||||
ctx.moveTo(x.value, y.value);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"line_to",
|
|
||||||
values.FN_NATIVE(([x, y]) => {
|
|
||||||
ctx.lineTo(x.value, y.value);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"arc",
|
|
||||||
values.FN_NATIVE(
|
|
||||||
([x, y, radius, startAngle, endAngle]) => {
|
|
||||||
ctx.arc(
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
radius.value,
|
|
||||||
startAngle.value,
|
|
||||||
endAngle.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"rect",
|
|
||||||
values.FN_NATIVE(([x, y, width, height]) => {
|
|
||||||
ctx.rect(
|
|
||||||
x.value,
|
|
||||||
y.value,
|
|
||||||
width.value,
|
|
||||||
height.value
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"fill",
|
|
||||||
values.FN_NATIVE(() => {
|
|
||||||
ctx.fill();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"stroke",
|
|
||||||
values.FN_NATIVE(() => {
|
|
||||||
ctx.stroke();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
"MkPages:chart": values.FN_NATIVE(([id, opts]) => {
|
|
||||||
/* TODO
|
|
||||||
utils.assertString(id);
|
|
||||||
utils.assertObject(opts);
|
|
||||||
const canvas = hpml.canvases[id.value];
|
|
||||||
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
|
|
||||||
Chart.defaults.color = '#555';
|
|
||||||
const chart = new Chart(canvas, {
|
|
||||||
type: opts.value.get('type').value,
|
|
||||||
data: {
|
|
||||||
labels: opts.value.get('labels').value.map(x => x.value),
|
|
||||||
datasets: opts.value.get('datasets').value.map(x => ({
|
|
||||||
label: x.value.has('label') ? x.value.get('label').value : '',
|
|
||||||
data: x.value.get('data').value.map(x => x.value),
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: x.value.has('color') ? x.value.get('color') : color,
|
|
||||||
backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: false,
|
|
||||||
devicePixelRatio: 1.5,
|
|
||||||
title: {
|
|
||||||
display: opts.value.has('title'),
|
|
||||||
text: opts.value.has('title') ? opts.value.get('title').value : '',
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 32,
|
|
||||||
right: 32,
|
|
||||||
top: opts.value.has('title') ? 16 : 32,
|
|
||||||
bottom: 16
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true,
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 16,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
chartArea: {
|
|
||||||
backgroundColor: '#fff'
|
|
||||||
},
|
|
||||||
...(opts.value.get('type').value === 'radar' ? {
|
|
||||||
scale: {
|
|
||||||
ticks: {
|
|
||||||
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false,
|
|
||||||
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
|
|
||||||
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
|
|
||||||
maxTicksLimit: 8,
|
|
||||||
},
|
|
||||||
pointLabels: {
|
|
||||||
fontSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} : {
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true,
|
|
||||||
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
|
|
||||||
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const funcDefs: Record<
|
|
||||||
string,
|
|
||||||
{ in: any[]; out: any; category: string; icon: any }
|
|
||||||
> = {
|
|
||||||
if: {
|
|
||||||
in: ["boolean", 0, 0],
|
|
||||||
out: 0,
|
|
||||||
category: "flow",
|
|
||||||
icon: "ph-share-network ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
for: {
|
|
||||||
in: ["number", "function"],
|
|
||||||
out: null,
|
|
||||||
category: "flow",
|
|
||||||
icon: "ph-recycle ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
not: {
|
|
||||||
in: ["boolean"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "logical",
|
|
||||||
icon: "ph-flag ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
or: {
|
|
||||||
in: ["boolean", "boolean"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "logical",
|
|
||||||
icon: "ph-flag ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
and: {
|
|
||||||
in: ["boolean", "boolean"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "logical",
|
|
||||||
icon: "ph-flag ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
add: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-plus ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
subtract: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-minus ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
multiply: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-x ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
divide: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-divide ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
mod: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-divide ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
round: {
|
|
||||||
in: ["number"],
|
|
||||||
out: "number",
|
|
||||||
category: "operation",
|
|
||||||
icon: "ph-calculator ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
eq: {
|
|
||||||
in: [0, 0],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-equals ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
notEq: {
|
|
||||||
in: [0, 0],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-prohibit-insert ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
gt: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-caret-right ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
lt: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-caret-left ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
gtEq: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-caret-double-right ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
ltEq: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "comparison",
|
|
||||||
icon: "ph-caret-double-right ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
strLen: {
|
|
||||||
in: ["string"],
|
|
||||||
out: "number",
|
|
||||||
category: "text",
|
|
||||||
icon: "ph-quotes ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
strPick: {
|
|
||||||
in: ["string", "number"],
|
|
||||||
out: "string",
|
|
||||||
category: "text",
|
|
||||||
icon: "ph-quotes ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
strReplace: {
|
|
||||||
in: ["string", "string", "string"],
|
|
||||||
out: "string",
|
|
||||||
category: "text",
|
|
||||||
icon: "ph-quotes ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
strReverse: {
|
|
||||||
in: ["string"],
|
|
||||||
out: "string",
|
|
||||||
category: "text",
|
|
||||||
icon: "ph-quotes ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
join: {
|
|
||||||
in: ["stringArray", "string"],
|
|
||||||
out: "string",
|
|
||||||
category: "text",
|
|
||||||
icon: "ph-quotes ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
stringToNumber: {
|
|
||||||
in: ["string"],
|
|
||||||
out: "number",
|
|
||||||
category: "convert",
|
|
||||||
icon: "ph-swap ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
numberToString: {
|
|
||||||
in: ["number"],
|
|
||||||
out: "string",
|
|
||||||
category: "convert",
|
|
||||||
icon: "ph-swap ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
splitStrByLine: {
|
|
||||||
in: ["string"],
|
|
||||||
out: "stringArray",
|
|
||||||
category: "convert",
|
|
||||||
icon: "ph-swap ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
pick: {
|
|
||||||
in: [null, "number"],
|
|
||||||
out: null,
|
|
||||||
category: "list",
|
|
||||||
icon: "ph-text-indent ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
listLen: {
|
|
||||||
in: [null],
|
|
||||||
out: "number",
|
|
||||||
category: "list",
|
|
||||||
icon: "ph-text-indent ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
rannum: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
dailyRannum: {
|
|
||||||
in: ["number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
seedRannum: {
|
|
||||||
in: [null, "number", "number"],
|
|
||||||
out: "number",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
random: {
|
|
||||||
in: ["number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
dailyRandom: {
|
|
||||||
in: ["number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
seedRandom: {
|
|
||||||
in: [null, "number"],
|
|
||||||
out: "boolean",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
randomPick: {
|
|
||||||
in: [0],
|
|
||||||
out: 0,
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
dailyRandomPick: {
|
|
||||||
in: [0],
|
|
||||||
out: 0,
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
seedRandomPick: {
|
|
||||||
in: [null, 0],
|
|
||||||
out: 0,
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
},
|
|
||||||
DRPWPM: {
|
|
||||||
in: ["stringArray"],
|
|
||||||
out: "string",
|
|
||||||
category: "random",
|
|
||||||
icon: "ph-dice-five ph-bold ph-lg",
|
|
||||||
}, // dailyRandomPickWithProbabilityMapping
|
|
||||||
};
|
|
||||||
|
|
||||||
export function initHpmlLib(
|
|
||||||
expr: Expr,
|
|
||||||
scope: HpmlScope,
|
|
||||||
randomSeed: string,
|
|
||||||
visitor?: any
|
|
||||||
) {
|
|
||||||
const date = new Date();
|
|
||||||
const day = `${visitor ? visitor.id : ""} ${date.getFullYear()}/${
|
|
||||||
date.getMonth() + 1
|
|
||||||
}/${date.getDate()}`;
|
|
||||||
|
|
||||||
// SHOULD be fine to ignore since it's intended + function shape isn't defined
|
|
||||||
const funcs: Record<string, Function> = {
|
|
||||||
not: (a: boolean) => !a,
|
|
||||||
or: (a: boolean, b: boolean) => a || b,
|
|
||||||
and: (a: boolean, b: boolean) => a && b,
|
|
||||||
eq: (a: any, b: any) => a === b,
|
|
||||||
notEq: (a: any, b: any) => a !== b,
|
|
||||||
gt: (a: number, b: number) => a > b,
|
|
||||||
lt: (a: number, b: number) => a < b,
|
|
||||||
gtEq: (a: number, b: number) => a >= b,
|
|
||||||
ltEq: (a: number, b: number) => a <= b,
|
|
||||||
if: (bool: boolean, a: any, b: any) => (bool ? a : b),
|
|
||||||
for: (times: number, fn: Fn) => {
|
|
||||||
const result: any[] = [];
|
|
||||||
for (let i = 0; i < times; i++) {
|
|
||||||
result.push(
|
|
||||||
fn.exec({
|
|
||||||
[fn.slots[0]]: i + 1,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
add: (a: number, b: number) => a + b,
|
|
||||||
subtract: (a: number, b: number) => a - b,
|
|
||||||
multiply: (a: number, b: number) => a * b,
|
|
||||||
divide: (a: number, b: number) => a / b,
|
|
||||||
mod: (a: number, b: number) => a % b,
|
|
||||||
round: (a: number) => Math.round(a),
|
|
||||||
strLen: (a: string) => a.length,
|
|
||||||
strPick: (a: string, b: number) => a[b - 1],
|
|
||||||
strReplace: (a: string, b: string, c: string) => a.split(b).join(c),
|
|
||||||
strReverse: (a: string) => a.split("").reverse().join(""),
|
|
||||||
join: (texts: string[], separator: string) =>
|
|
||||||
texts.join(separator || ""),
|
|
||||||
stringToNumber: (a: string) => parseInt(a),
|
|
||||||
numberToString: (a: number) => a.toString(),
|
|
||||||
splitStrByLine: (a: string) => a.split("\n"),
|
|
||||||
pick: (list: any[], i: number) => list[i - 1],
|
|
||||||
listLen: (list: any[]) => list.length,
|
|
||||||
random: (probability: number) =>
|
|
||||||
Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) <
|
|
||||||
probability,
|
|
||||||
rannum: (min: number, max: number) =>
|
|
||||||
min +
|
|
||||||
Math.floor(
|
|
||||||
seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)
|
|
||||||
),
|
|
||||||
randomPick: (list: any[]) =>
|
|
||||||
list[
|
|
||||||
Math.floor(
|
|
||||||
seedrandom(`${randomSeed}:${expr.id}`)() * list.length
|
|
||||||
)
|
|
||||||
],
|
|
||||||
dailyRandom: (probability: number) =>
|
|
||||||
Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability,
|
|
||||||
dailyRannum: (min: number, max: number) =>
|
|
||||||
min +
|
|
||||||
Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)),
|
|
||||||
dailyRandomPick: (list: any[]) =>
|
|
||||||
list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)],
|
|
||||||
seedRandom: (seed: any, probability: number) =>
|
|
||||||
Math.floor(seedrandom(seed)() * 100) < probability,
|
|
||||||
seedRannum: (seed: any, min: number, max: number) =>
|
|
||||||
min + Math.floor(seedrandom(seed)() * (max - min + 1)),
|
|
||||||
seedRandomPick: (seed: any, list: any[]) =>
|
|
||||||
list[Math.floor(seedrandom(seed)() * list.length)],
|
|
||||||
DRPWPM: (list: string[]) => {
|
|
||||||
const xs: any[] = [];
|
|
||||||
let totalFactor = 0;
|
|
||||||
for (const x of list) {
|
|
||||||
const parts = x.split(" ");
|
|
||||||
const factor = parseInt(parts.pop()!, 10);
|
|
||||||
const text = parts.join(" ");
|
|
||||||
totalFactor += factor;
|
|
||||||
xs.push({ factor, text });
|
|
||||||
}
|
|
||||||
const r = seedrandom(`${day}:${expr.id}`)() * totalFactor;
|
|
||||||
let stackedFactor = 0;
|
|
||||||
for (const x of xs) {
|
|
||||||
if (r >= stackedFactor && r <= stackedFactor + x.factor) {
|
|
||||||
return x.text;
|
|
||||||
} else {
|
|
||||||
stackedFactor += x.factor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xs[0].text;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return funcs;
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
import autobind from "autobind-decorator";
|
|
||||||
import { Type, envVarsDef, PageVar } from ".";
|
|
||||||
import { Expr, isLiteralValue, Variable } from "./expr";
|
|
||||||
import { funcDefs } from "./lib";
|
|
||||||
|
|
||||||
type TypeError = {
|
|
||||||
arg: number;
|
|
||||||
expect: Type;
|
|
||||||
actual: Type;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hpml type checker
|
|
||||||
*/
|
|
||||||
export class HpmlTypeChecker {
|
|
||||||
public variables: Variable[];
|
|
||||||
public pageVars: PageVar[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
variables: HpmlTypeChecker["variables"] = [],
|
|
||||||
pageVars: HpmlTypeChecker["pageVars"] = []
|
|
||||||
) {
|
|
||||||
this.variables = variables;
|
|
||||||
this.pageVars = pageVars;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public typeCheck(v: Expr): TypeError | null {
|
|
||||||
if (isLiteralValue(v)) return null;
|
|
||||||
|
|
||||||
const def = funcDefs[v.type || ""];
|
|
||||||
if (def == null) {
|
|
||||||
throw new Error(`Unknown type: ${v.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const generic: Type[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < def.in.length; i++) {
|
|
||||||
const arg = def.in[i];
|
|
||||||
const type = this.infer(v.args[i]);
|
|
||||||
if (type === null) continue;
|
|
||||||
|
|
||||||
if (typeof arg === "number") {
|
|
||||||
if (generic[arg] === undefined) {
|
|
||||||
generic[arg] = type;
|
|
||||||
} else if (type !== generic[arg]) {
|
|
||||||
return {
|
|
||||||
arg: i,
|
|
||||||
expect: generic[arg],
|
|
||||||
actual: type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (type !== arg) {
|
|
||||||
return {
|
|
||||||
arg: i,
|
|
||||||
expect: arg,
|
|
||||||
actual: type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public getExpectedType(v: Expr, slot: number): Type {
|
|
||||||
const def = funcDefs[v.type || ""];
|
|
||||||
if (def == null) {
|
|
||||||
throw new Error(`Unknown type: ${v.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const generic: Type[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < def.in.length; i++) {
|
|
||||||
const arg = def.in[i];
|
|
||||||
const type = this.infer(v.args[i]);
|
|
||||||
if (type === null) continue;
|
|
||||||
|
|
||||||
if (typeof arg === "number") {
|
|
||||||
if (generic[arg] === undefined) {
|
|
||||||
generic[arg] = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof def.in[slot] === "number") {
|
|
||||||
return generic[def.in[slot]] || null;
|
|
||||||
} else {
|
|
||||||
return def.in[slot];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public infer(v: Expr): Type {
|
|
||||||
if (v.type === null) return null;
|
|
||||||
if (v.type === "text") return "string";
|
|
||||||
if (v.type === "multiLineText") return "string";
|
|
||||||
if (v.type === "textList") return "stringArray";
|
|
||||||
if (v.type === "number") return "number";
|
|
||||||
if (v.type === "ref") {
|
|
||||||
const variable = this.variables.find((va) => va.name === v.value);
|
|
||||||
if (variable) {
|
|
||||||
return this.infer(variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageVar = this.pageVars.find((va) => va.name === v.value);
|
|
||||||
if (pageVar) {
|
|
||||||
return pageVar.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envVar = envVarsDef[v.value || ""];
|
|
||||||
if (envVar !== undefined) {
|
|
||||||
return envVar;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (v.type === "aiScriptVar") return null;
|
|
||||||
if (v.type === "fn") return null; // todo
|
|
||||||
if (v.type.startsWith("fn:")) return null; // todo
|
|
||||||
|
|
||||||
const generic: Type[] = [];
|
|
||||||
|
|
||||||
const def = funcDefs[v.type];
|
|
||||||
|
|
||||||
for (let i = 0; i < def.in.length; i++) {
|
|
||||||
const arg = def.in[i];
|
|
||||||
if (typeof arg === "number") {
|
|
||||||
const type = this.infer(v.args[i]);
|
|
||||||
|
|
||||||
if (generic[arg] === undefined) {
|
|
||||||
generic[arg] = type;
|
|
||||||
} else {
|
|
||||||
if (type !== generic[arg]) {
|
|
||||||
generic[arg] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof def.out === "number") {
|
|
||||||
return generic[def.out];
|
|
||||||
} else {
|
|
||||||
return def.out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public getVarByName(name: string): Variable {
|
|
||||||
const v = this.variables.find((x) => x.name === name);
|
|
||||||
if (v !== undefined) {
|
|
||||||
return v;
|
|
||||||
} else {
|
|
||||||
throw new Error(`No such variable '${name}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public getVarsByType(type: Type): Variable[] {
|
|
||||||
if (type == null) return this.variables;
|
|
||||||
return this.variables.filter(
|
|
||||||
(x) => this.infer(x) === null || this.infer(x) === type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public getEnvVarsByType(type: Type): string[] {
|
|
||||||
if (type == null) return Object.keys(envVarsDef);
|
|
||||||
return Object.entries(envVarsDef)
|
|
||||||
.filter(([k, v]) => v === null || type === v)
|
|
||||||
.map(([k, v]) => k);
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public getPageVarsByType(type: Type): string[] {
|
|
||||||
if (type == null) return this.pageVars.map((v) => v.name);
|
|
||||||
return this.pageVars.filter((v) => type === v.type).map((v) => v.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public isUsedName(name: string) {
|
|
||||||
if (this.variables.some((v) => v.name === name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pageVars.some((v) => v.name === name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envVarsDef[name]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,17 +6,6 @@ import { Storage } from "./pizzax";
|
||||||
import lightTheme from "@/themes/l-rosepinedawn.json5";
|
import lightTheme from "@/themes/l-rosepinedawn.json5";
|
||||||
import darkTheme from "@/themes/d-rosepine.json5";
|
import darkTheme from "@/themes/d-rosepine.json5";
|
||||||
|
|
||||||
export const postFormActions: {
|
|
||||||
title: any;
|
|
||||||
handler: (form: any, update: any) => void;
|
|
||||||
}[] = [];
|
|
||||||
export const userActions: { title: any; handler: (user: any) => void }[] = [];
|
|
||||||
export const noteActions: { title: any; handler: (note: any) => void }[] = [];
|
|
||||||
export const noteViewInterruptors: { handler: (note: any) => Promise<any> }[] =
|
|
||||||
[];
|
|
||||||
export const notePostInterruptors: { handler: (note: any) => Promise<any> }[] =
|
|
||||||
[];
|
|
||||||
|
|
||||||
const menuOptions = [
|
const menuOptions = [
|
||||||
"notifications",
|
"notifications",
|
||||||
"followRequests",
|
"followRequests",
|
||||||
|
|
|
@ -735,35 +735,6 @@ hr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
._anime_bounce {
|
|
||||||
will-change: transform;
|
|
||||||
animation: bounce ease 0.7s;
|
|
||||||
animation-iteration-count: 1;
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
}
|
|
||||||
._anime_bounce_ready {
|
|
||||||
will-change: transform;
|
|
||||||
transform: scaleX(0.9) scaleY(0.9);
|
|
||||||
}
|
|
||||||
._anime_bounce_standBy {
|
|
||||||
transition: transform 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
0% {
|
|
||||||
transform: scaleX(0.9) scaleY(0.9);
|
|
||||||
}
|
|
||||||
19% {
|
|
||||||
transform: scaleX(1.1) scaleY(1.1);
|
|
||||||
}
|
|
||||||
48% {
|
|
||||||
transform: scaleX(0.95) scaleY(0.95);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scaleX(1) scaleY(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ph-xxs {
|
.ph-xxs {
|
||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
:style="{ backgroundImage: `url(${$i.bannerUrl})` }"
|
:style="{ backgroundImage: `url(${$i.bannerUrl})` }"
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="
|
v-tooltip.noDelay.right="
|
||||||
`${i18n.ts.account}: @${$i.username}`
|
`${i18n.ts.account}: @${$i.username}`
|
||||||
"
|
"
|
||||||
|
@ -23,13 +22,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="middle">
|
<div class="middle">
|
||||||
<MkA
|
<MkA class="item index" active-class="active" to="/" exact>
|
||||||
v-click-anime
|
|
||||||
class="item index"
|
|
||||||
active-class="active"
|
|
||||||
to="/"
|
|
||||||
exact
|
|
||||||
>
|
|
||||||
<i class="icon ph-house ph-bold ph-lg ph-fw ph-lg"></i
|
<i class="icon ph-house ph-bold ph-lg ph-fw ph-lg"></i
|
||||||
><span class="text">{{ i18n.ts.timeline }}</span>
|
><span class="text">{{ i18n.ts.timeline }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
|
@ -41,7 +34,6 @@
|
||||||
navbarItemDef[item] &&
|
navbarItemDef[item] &&
|
||||||
navbarItemDef[item].show !== false
|
navbarItemDef[item].show !== false
|
||||||
"
|
"
|
||||||
v-click-anime
|
|
||||||
class="item _button"
|
class="item _button"
|
||||||
:class="[item, { active: navbarItemDef[item].active }]"
|
:class="[item, { active: navbarItemDef[item].active }]"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
|
@ -69,7 +61,6 @@
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<MkA
|
<MkA
|
||||||
v-if="$i.isAdmin || $i.isModerator"
|
v-if="$i.isAdmin || $i.isModerator"
|
||||||
v-click-anime
|
|
||||||
class="item"
|
class="item"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
to="/admin"
|
to="/admin"
|
||||||
|
@ -77,7 +68,7 @@
|
||||||
<i class="icon ph-door ph-bold ph-lg ph-fw ph-lg"></i
|
<i class="icon ph-door ph-bold ph-lg ph-fw ph-lg"></i
|
||||||
><span class="text">{{ i18n.ts.controlPanel }}</span>
|
><span class="text">{{ i18n.ts.controlPanel }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button v-click-anime class="item _button" @click="more">
|
<button class="item _button" @click="more">
|
||||||
<i
|
<i
|
||||||
class="icon ph-dots-three-outline ph-bold ph-lg ph-fw ph-lg"
|
class="icon ph-dots-three-outline ph-bold ph-lg ph-fw ph-lg"
|
||||||
></i
|
></i
|
||||||
|
@ -86,12 +77,7 @@
|
||||||
><i class="icon ph-circle ph-fill"></i
|
><i class="icon ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<MkA
|
<MkA class="item" active-class="active" to="/settings">
|
||||||
v-click-anime
|
|
||||||
class="item"
|
|
||||||
active-class="active"
|
|
||||||
to="/settings"
|
|
||||||
>
|
|
||||||
<i class="icon ph-gear-six ph-bold ph-lg ph-fw ph-lg"></i
|
<i class="icon ph-gear-six ph-bold ph-lg ph-fw ph-lg"></i
|
||||||
><span class="text">{{ i18n.ts.settings }}</span>
|
><span class="text">{{ i18n.ts.settings }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
:style="{ backgroundImage: `url(${$i.bannerUrl})` }"
|
:style="{ backgroundImage: `url(${$i.bannerUrl})` }"
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="
|
v-tooltip.noDelay.right="
|
||||||
`${i18n.ts.account}: @${$i.username}`
|
`${i18n.ts.account}: @${$i.username}`
|
||||||
"
|
"
|
||||||
|
@ -24,7 +23,6 @@
|
||||||
</div>
|
</div>
|
||||||
<nav class="middle">
|
<nav class="middle">
|
||||||
<MkA
|
<MkA
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="i18n.ts.timeline"
|
v-tooltip.noDelay.right="i18n.ts.timeline"
|
||||||
class="item index"
|
class="item index"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
|
@ -42,7 +40,6 @@
|
||||||
navbarItemDef[item] &&
|
navbarItemDef[item] &&
|
||||||
navbarItemDef[item].show !== false
|
navbarItemDef[item].show !== false
|
||||||
"
|
"
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="
|
v-tooltip.noDelay.right="
|
||||||
i18n.ts[navbarItemDef[item].title]
|
i18n.ts[navbarItemDef[item].title]
|
||||||
"
|
"
|
||||||
|
@ -73,7 +70,6 @@
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<MkA
|
<MkA
|
||||||
v-if="$i.isAdmin || $i.isModerator"
|
v-if="$i.isAdmin || $i.isModerator"
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="i18n.ts.controlPanel"
|
v-tooltip.noDelay.right="i18n.ts.controlPanel"
|
||||||
class="item _button"
|
class="item _button"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
|
@ -92,7 +88,6 @@
|
||||||
><span class="text">{{ i18n.ts.controlPanel }}</span>
|
><span class="text">{{ i18n.ts.controlPanel }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button
|
<button
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="i18n.ts.more"
|
v-tooltip.noDelay.right="i18n.ts.more"
|
||||||
class="item _button"
|
class="item _button"
|
||||||
@click="more"
|
@click="more"
|
||||||
|
@ -106,7 +101,6 @@
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<MkA
|
<MkA
|
||||||
v-click-anime
|
|
||||||
v-tooltip.noDelay.right="i18n.ts.settings"
|
v-tooltip.noDelay.right="i18n.ts.settings"
|
||||||
class="item _button"
|
class="item _button"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
>
|
>
|
||||||
<small
|
<small
|
||||||
>Powered by
|
>Powered by
|
||||||
<a href="https://git.astolfo.cool/natty/magnetar" target="_blank"
|
<a
|
||||||
|
href="https://git.astolfo.cool/natty/magnetar"
|
||||||
|
target="_blank"
|
||||||
>Magnetar</a
|
>Magnetar</a
|
||||||
></small
|
></small
|
||||||
>
|
>
|
||||||
|
@ -45,10 +47,6 @@
|
||||||
><i class="ph-compass ph-bold ph-lg icon"></i
|
><i class="ph-compass ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.explore }}</MkA
|
>{{ i18n.ts.explore }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/pages" class="link" active-class="active"
|
|
||||||
><i class="ph-file-text ph-bold ph-lg icon"></i
|
|
||||||
>{{ i18n.ts.pages }}</MkA
|
|
||||||
>
|
|
||||||
<MkA to="/gallery" class="link" active-class="active"
|
<MkA to="/gallery" class="link" active-class="active"
|
||||||
><i class="ph-image-square ph-bold ph-lg icon"></i
|
><i class="ph-image-square ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.gallery }}</MkA
|
>{{ i18n.ts.gallery }}</MkA
|
||||||
|
@ -74,17 +72,11 @@ import { host, instanceName } from "@/config";
|
||||||
import { search } from "@/scripts/search";
|
import { search } from "@/scripts/search";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import XSigninDialog from "@/components/MkSigninDialog.vue";
|
import XSigninDialog from "@/components/MkSigninDialog.vue";
|
||||||
import XSignupDialog from "@/components/MkSignupDialog.vue";
|
import XSignupDialog from "@/components/MkSignupDialog.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import { ColdDeviceStorage, defaultStore } from "@/store";
|
import { ColdDeviceStorage, defaultStore } from "@/store";
|
||||||
import { mainRouter } from "@/router";
|
import { mainRouter } from "@/router";
|
||||||
import {
|
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
PageMetadata,
|
|
||||||
provideMetadataReceiver,
|
|
||||||
setPageMetadata,
|
|
||||||
} from "@/scripts/page-metadata";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
const DESKTOP_THRESHOLD = 1000;
|
const DESKTOP_THRESHOLD = 1000;
|
||||||
|
|
|
@ -18,10 +18,6 @@
|
||||||
><i class="ph-compass ph-bold ph-lg icon"></i
|
><i class="ph-compass ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.explore }}</MkA
|
>{{ i18n.ts.explore }}</MkA
|
||||||
>
|
>
|
||||||
<MkA to="/pages" class="link" active-class="active"
|
|
||||||
><i class="ph-file-text ph-bold ph-lg icon"></i
|
|
||||||
>{{ i18n.ts.pages }}</MkA
|
|
||||||
>
|
|
||||||
<MkA to="/gallery" class="link" active-class="active"
|
<MkA to="/gallery" class="link" active-class="active"
|
||||||
><i class="ph-image-square ph-bold ph-lg icon"></i
|
><i class="ph-image-square ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.gallery }}</MkA
|
>{{ i18n.ts.gallery }}</MkA
|
||||||
|
|
|
@ -1,206 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscript">
|
|
||||||
<template #header
|
|
||||||
><i class="ph-terminal-window ph-bold ph-lg"></i
|
|
||||||
>{{ i18n.ts._widgets.aiscript }}</template
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="uylguesu _monospace">
|
|
||||||
<textarea
|
|
||||||
v-model="widgetProps.script"
|
|
||||||
placeholder="(1 + 1)"
|
|
||||||
></textarea>
|
|
||||||
<button class="_buttonPrimary" @click="run">RUN</button>
|
|
||||||
<div class="logs">
|
|
||||||
<div
|
|
||||||
v-for="log in logs"
|
|
||||||
:key="log.id"
|
|
||||||
class="log"
|
|
||||||
:class="{ print: log.print }"
|
|
||||||
>
|
|
||||||
{{ log.text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
||||||
import { AiScript, parse, utils } from "@syuilo/aiscript";
|
|
||||||
import {
|
|
||||||
useWidgetPropsManager,
|
|
||||||
Widget,
|
|
||||||
WidgetComponentEmits,
|
|
||||||
WidgetComponentExpose,
|
|
||||||
WidgetComponentProps,
|
|
||||||
} from "./widget";
|
|
||||||
import { GetFormResultType } from "@/scripts/form";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
|
||||||
import { createAiScriptEnv } from "@/scripts/aiscript/api";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const name = "aiscript";
|
|
||||||
|
|
||||||
const widgetPropsDef = {
|
|
||||||
showHeader: {
|
|
||||||
type: "boolean" as const,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
script: {
|
|
||||||
type: "string" as const,
|
|
||||||
multiline: true,
|
|
||||||
default: "(1 + 1)",
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
|
||||||
|
|
||||||
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
|
||||||
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
|
||||||
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
|
||||||
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
|
|
||||||
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
|
|
||||||
|
|
||||||
const { widgetProps, configure } = useWidgetPropsManager(
|
|
||||||
name,
|
|
||||||
widgetPropsDef,
|
|
||||||
props,
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const logs = ref<
|
|
||||||
{
|
|
||||||
id: string;
|
|
||||||
text: string;
|
|
||||||
print: boolean;
|
|
||||||
}[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
const run = async () => {
|
|
||||||
logs.value = [];
|
|
||||||
const aiscript = new AiScript(
|
|
||||||
createAiScriptEnv({
|
|
||||||
storageKey: "widget",
|
|
||||||
token: $i?.token,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
in: (q) => {
|
|
||||||
return new Promise((ok) => {
|
|
||||||
os.inputText({
|
|
||||||
title: q,
|
|
||||||
}).then(({ canceled, result: a }) => {
|
|
||||||
ok(a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
out: (value) => {
|
|
||||||
logs.value.push({
|
|
||||||
id: Math.random().toString(),
|
|
||||||
text:
|
|
||||||
value.type === "str"
|
|
||||||
? value.value
|
|
||||||
: utils.valToString(value),
|
|
||||||
print: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
log: (type, params) => {
|
|
||||||
switch (type) {
|
|
||||||
case "end":
|
|
||||||
logs.value.push({
|
|
||||||
id: Math.random().toString(),
|
|
||||||
text: utils.valToString(params.val, true),
|
|
||||||
print: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(widgetProps.script);
|
|
||||||
} catch (err) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "Syntax error :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await aiscript.exec(ast);
|
|
||||||
} catch (err) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
|
||||||
name,
|
|
||||||
configure,
|
|
||||||
id: props.widget ? props.widget.id : null,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.uylguesu {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
> textarea {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
color: var(--fg);
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-bottom: solid 0.5px var(--divider);
|
|
||||||
border-radius: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font: inherit;
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 8px;
|
|
||||||
padding: 0 10px;
|
|
||||||
height: 28px;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .logs {
|
|
||||||
border-top: solid 0.5px var(--divider);
|
|
||||||
text-align: left;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .log {
|
|
||||||
&:not(.print) {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,113 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mkw-button">
|
|
||||||
<MkButton :primary="widgetProps.colored" full @click="run">
|
|
||||||
{{ widgetProps.label }}
|
|
||||||
</MkButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
||||||
import { AiScript, parse, utils } from "@syuilo/aiscript";
|
|
||||||
import {
|
|
||||||
useWidgetPropsManager,
|
|
||||||
Widget,
|
|
||||||
WidgetComponentEmits,
|
|
||||||
WidgetComponentExpose,
|
|
||||||
WidgetComponentProps,
|
|
||||||
} from "./widget";
|
|
||||||
import { GetFormResultType } from "@/scripts/form";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { createAiScriptEnv } from "@/scripts/aiscript/api";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
|
|
||||||
const name = "button";
|
|
||||||
|
|
||||||
const widgetPropsDef = {
|
|
||||||
label: {
|
|
||||||
type: "string" as const,
|
|
||||||
default: "BUTTON",
|
|
||||||
},
|
|
||||||
colored: {
|
|
||||||
type: "boolean" as const,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
script: {
|
|
||||||
type: "string" as const,
|
|
||||||
multiline: true,
|
|
||||||
default: 'Mk:dialog("hello" "world")',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
|
||||||
|
|
||||||
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
|
||||||
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
|
||||||
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
|
||||||
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
|
|
||||||
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
|
|
||||||
|
|
||||||
const { widgetProps, configure } = useWidgetPropsManager(
|
|
||||||
name,
|
|
||||||
widgetPropsDef,
|
|
||||||
props,
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const run = async () => {
|
|
||||||
const aiscript = new AiScript(
|
|
||||||
createAiScriptEnv({
|
|
||||||
storageKey: "widget",
|
|
||||||
token: $i?.token,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
in: (q) => {
|
|
||||||
return new Promise((ok) => {
|
|
||||||
os.inputText({
|
|
||||||
title: q,
|
|
||||||
}).then(({ canceled, result: a }) => {
|
|
||||||
ok(a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
out: (value) => {
|
|
||||||
// nop
|
|
||||||
},
|
|
||||||
log: (type, params) => {
|
|
||||||
// nop
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(widgetProps.script);
|
|
||||||
} catch (err) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: "Syntax error :(",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await aiscript.exec(ast);
|
|
||||||
} catch (err) {
|
|
||||||
os.alert({
|
|
||||||
type: "error",
|
|
||||||
text: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
|
||||||
name,
|
|
||||||
configure,
|
|
||||||
id: props.widget ? props.widget.id : null,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mkw-button {
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -61,10 +61,6 @@ export default function (app: App) {
|
||||||
"MkwSlideshow",
|
"MkwSlideshow",
|
||||||
defineAsyncComponent(() => import("./slideshow.vue"))
|
defineAsyncComponent(() => import("./slideshow.vue"))
|
||||||
);
|
);
|
||||||
app.component(
|
|
||||||
"MkwServerMetric",
|
|
||||||
defineAsyncComponent(() => import("./server-metric/index.vue"))
|
|
||||||
);
|
|
||||||
app.component(
|
app.component(
|
||||||
"MkwOnlineUsers",
|
"MkwOnlineUsers",
|
||||||
defineAsyncComponent(() => import("./online-users.vue"))
|
defineAsyncComponent(() => import("./online-users.vue"))
|
||||||
|
@ -73,18 +69,6 @@ export default function (app: App) {
|
||||||
"MkwJobQueue",
|
"MkwJobQueue",
|
||||||
defineAsyncComponent(() => import("./job-queue.vue"))
|
defineAsyncComponent(() => import("./job-queue.vue"))
|
||||||
);
|
);
|
||||||
app.component(
|
|
||||||
"MkwInstanceCloud",
|
|
||||||
defineAsyncComponent(() => import("./instance-cloud.vue"))
|
|
||||||
);
|
|
||||||
app.component(
|
|
||||||
"MkwButton",
|
|
||||||
defineAsyncComponent(() => import("./button.vue"))
|
|
||||||
);
|
|
||||||
app.component(
|
|
||||||
"MkwAiscript",
|
|
||||||
defineAsyncComponent(() => import("./aiscript.vue"))
|
|
||||||
);
|
|
||||||
app.component(
|
app.component(
|
||||||
"MkwUserList",
|
"MkwUserList",
|
||||||
defineAsyncComponent(() => import("./user-list.vue"))
|
defineAsyncComponent(() => import("./user-list.vue"))
|
||||||
|
@ -110,13 +94,9 @@ export const widgets = [
|
||||||
"digitalClock",
|
"digitalClock",
|
||||||
"unixClock",
|
"unixClock",
|
||||||
"federation",
|
"federation",
|
||||||
"instanceCloud",
|
|
||||||
"postForm",
|
"postForm",
|
||||||
"slideshow",
|
"slideshow",
|
||||||
"serverMetric",
|
|
||||||
"serverInfo",
|
"serverInfo",
|
||||||
"onlineUsers",
|
"onlineUsers",
|
||||||
"jobQueue",
|
"jobQueue",
|
||||||
"button",
|
|
||||||
"aiscript",
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkContainer
|
|
||||||
:naked="widgetProps.transparent"
|
|
||||||
:show-header="false"
|
|
||||||
class="mkw-instance-cloud"
|
|
||||||
>
|
|
||||||
<div class="">
|
|
||||||
<MkTagCloud v-if="activeInstances">
|
|
||||||
<li v-for="instance in activeInstances" :key="instance.id">
|
|
||||||
<a @click.prevent="onInstanceClick(instance)">
|
|
||||||
<img
|
|
||||||
style="width: 32px"
|
|
||||||
:src="getInstanceIcon(instance)"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</MkTagCloud>
|
|
||||||
</div>
|
|
||||||
</MkContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import {
|
|
||||||
useWidgetPropsManager,
|
|
||||||
WidgetComponentEmits,
|
|
||||||
WidgetComponentProps,
|
|
||||||
} from "./widget";
|
|
||||||
import type { Widget, WidgetComponentExpose } from "./widget";
|
|
||||||
import type { GetFormResultType } from "@/scripts/form";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
|
||||||
import MkTagCloud from "@/components/MkTagCloud.vue";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { useInterval } from "@/scripts/use-interval";
|
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
|
||||||
|
|
||||||
const name = "instanceCloud";
|
|
||||||
|
|
||||||
const widgetPropsDef = {
|
|
||||||
transparent: {
|
|
||||||
type: "boolean" as const,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
|
||||||
|
|
||||||
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
|
||||||
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
|
||||||
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
|
||||||
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
|
|
||||||
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
|
|
||||||
|
|
||||||
const { widgetProps, configure } = useWidgetPropsManager(
|
|
||||||
name,
|
|
||||||
widgetPropsDef,
|
|
||||||
props,
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
let cloud = $ref<InstanceType<typeof MkTagCloud> | null>();
|
|
||||||
let activeInstances = $shallowRef(null);
|
|
||||||
|
|
||||||
function onInstanceClick(i) {
|
|
||||||
os.pageWindow(`/instance-info/${i.host}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
useInterval(
|
|
||||||
() => {
|
|
||||||
os.api("federation/instances", {
|
|
||||||
sort: "+lastCommunicatedAt",
|
|
||||||
limit: 25,
|
|
||||||
}).then((res) => {
|
|
||||||
activeInstances = res;
|
|
||||||
if (cloud) cloud.update();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
1000 * 60 * 3,
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
afterMounted: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function getInstanceIcon(instance): string {
|
|
||||||
return (
|
|
||||||
getProxiedImageUrlNullable(instance.iconUrl, "preview") ??
|
|
||||||
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
|
|
||||||
"/client-assets/dummy.png"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
|
||||||
name,
|
|
||||||
configure,
|
|
||||||
id: props.widget ? props.widget.id : null,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -1,193 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="lcfyofjk">
|
|
||||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
|
||||||
<defs>
|
|
||||||
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
|
|
||||||
<stop offset="0%" stop-color="hsl(189, 43%, 73%)"></stop>
|
|
||||||
<stop offset="100%" stop-color="hsl(343, 76%, 68%)"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<mask
|
|
||||||
:id="cpuMaskId"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
:width="viewBoxX"
|
|
||||||
:height="viewBoxY"
|
|
||||||
>
|
|
||||||
<polygon
|
|
||||||
:points="cpuPolygonPoints"
|
|
||||||
fill="#fff"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
/>
|
|
||||||
<polyline
|
|
||||||
:points="cpuPolylinePoints"
|
|
||||||
fill="none"
|
|
||||||
stroke="#fff"
|
|
||||||
stroke-width="1"
|
|
||||||
/>
|
|
||||||
<circle :cx="cpuHeadX" :cy="cpuHeadY" r="1.5" fill="#fff" />
|
|
||||||
</mask>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
x="-2"
|
|
||||||
y="-2"
|
|
||||||
:width="viewBoxX + 4"
|
|
||||||
:height="viewBoxY + 4"
|
|
||||||
:style="`stroke: none; fill: url(#${cpuGradientId}); mask: url(#${cpuMaskId})`"
|
|
||||||
/>
|
|
||||||
<text x="1" y="5">
|
|
||||||
CPU
|
|
||||||
<tspan>{{ cpuP }}%</tspan>
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
|
||||||
<defs>
|
|
||||||
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
|
|
||||||
<stop offset="0%" stop-color="hsl(189, 43%, 73%)"></stop>
|
|
||||||
<stop offset="100%" stop-color="hsl(343, 76%, 68%)"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<mask
|
|
||||||
:id="memMaskId"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
:width="viewBoxX"
|
|
||||||
:height="viewBoxY"
|
|
||||||
>
|
|
||||||
<polygon
|
|
||||||
:points="memPolygonPoints"
|
|
||||||
fill="#fff"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
/>
|
|
||||||
<polyline
|
|
||||||
:points="memPolylinePoints"
|
|
||||||
fill="none"
|
|
||||||
stroke="#fff"
|
|
||||||
stroke-width="1"
|
|
||||||
/>
|
|
||||||
<circle :cx="memHeadX" :cy="memHeadY" r="1.5" fill="#fff" />
|
|
||||||
</mask>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
x="-2"
|
|
||||||
y="-2"
|
|
||||||
:width="viewBoxX + 4"
|
|
||||||
:height="viewBoxY + 4"
|
|
||||||
:style="`stroke: none; fill: url(#${memGradientId}); mask: url(#${memMaskId})`"
|
|
||||||
/>
|
|
||||||
<text x="1" y="5">
|
|
||||||
MEM
|
|
||||||
<tspan>{{ memP }}%</tspan>
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onBeforeUnmount } from "vue";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
connection: any;
|
|
||||||
meta: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let viewBoxX: number = $ref(50);
|
|
||||||
let viewBoxY: number = $ref(30);
|
|
||||||
let stats: any[] = $ref([]);
|
|
||||||
const cpuGradientId = uuid();
|
|
||||||
const cpuMaskId = uuid();
|
|
||||||
const memGradientId = uuid();
|
|
||||||
const memMaskId = uuid();
|
|
||||||
let cpuPolylinePoints: string = $ref("");
|
|
||||||
let memPolylinePoints: string = $ref("");
|
|
||||||
let cpuPolygonPoints: string = $ref("");
|
|
||||||
let memPolygonPoints: string = $ref("");
|
|
||||||
let cpuHeadX: any = $ref(null);
|
|
||||||
let cpuHeadY: any = $ref(null);
|
|
||||||
let memHeadX: any = $ref(null);
|
|
||||||
let memHeadY: any = $ref(null);
|
|
||||||
let cpuP: string = $ref("");
|
|
||||||
let memP: string = $ref("");
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.connection.on("stats", onStats);
|
|
||||||
props.connection.on("statsLog", onStatsLog);
|
|
||||||
props.connection.send("requestLog", {
|
|
||||||
id: Math.random().toString().substr(2, 8),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
props.connection.off("stats", onStats);
|
|
||||||
props.connection.off("statsLog", onStatsLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
function onStats(connStats) {
|
|
||||||
stats.push(connStats);
|
|
||||||
if (stats.length > 50) stats.shift();
|
|
||||||
|
|
||||||
let cpuPolylinePointsStats = stats.map((s, i) => [
|
|
||||||
viewBoxX - (stats.length - 1 - i),
|
|
||||||
(1 - s.cpu) * viewBoxY,
|
|
||||||
]);
|
|
||||||
let memPolylinePointsStats = stats.map((s, i) => [
|
|
||||||
viewBoxX - (stats.length - 1 - i),
|
|
||||||
(1 - s.mem.active / props.meta.mem.total) * viewBoxY,
|
|
||||||
]);
|
|
||||||
cpuPolylinePoints = cpuPolylinePointsStats
|
|
||||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
|
||||||
.join(" ");
|
|
||||||
memPolylinePoints = memPolylinePointsStats
|
|
||||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
cpuPolygonPoints = `${
|
|
||||||
viewBoxX - (stats.length - 1)
|
|
||||||
},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
|
||||||
memPolygonPoints = `${
|
|
||||||
viewBoxX - (stats.length - 1)
|
|
||||||
},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
|
||||||
|
|
||||||
cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0];
|
|
||||||
cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1];
|
|
||||||
memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0];
|
|
||||||
memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1];
|
|
||||||
|
|
||||||
cpuP = (connStats.cpu * 100).toFixed(0);
|
|
||||||
memP = ((connStats.mem.active / props.meta.mem.total) * 100).toFixed(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStatsLog(statsLog) {
|
|
||||||
for (const revStats of [...statsLog].reverse()) {
|
|
||||||
onStats(revStats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.lcfyofjk {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> svg {
|
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> text {
|
|
||||||
font-size: 5px;
|
|
||||||
fill: currentColor;
|
|
||||||
|
|
||||||
> tspan {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="vrvdvrys">
|
|
||||||
<XPie class="pie" :value="usage" />
|
|
||||||
<div>
|
|
||||||
<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
|
|
||||||
<p>{{ meta.cpu.cores }} Logical cores</p>
|
|
||||||
<p>{{ meta.cpu.model }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onBeforeUnmount } from "vue";
|
|
||||||
import XPie from "./pie.vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
connection: any;
|
|
||||||
meta: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let usage: number = $ref(0);
|
|
||||||
|
|
||||||
function onStats(stats) {
|
|
||||||
usage = stats.cpu;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.connection.on("stats", onStats);
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
props.connection.off("stats", onStats);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vrvdvrys {
|
|
||||||
display: flex;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .pie {
|
|
||||||
height: 82px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="zbwaqsat">
|
|
||||||
<XPie class="pie" :value="usage" />
|
|
||||||
<div>
|
|
||||||
<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
|
|
||||||
<p>Total: {{ bytes(total, 1) }}</p>
|
|
||||||
<p>Free: {{ bytes(available, 1) }}</p>
|
|
||||||
<p>Used: {{ bytes(used, 1) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import XPie from "./pie.vue";
|
|
||||||
import bytes from "@/filters/bytes";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
meta: any; // TODO
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const usage = $computed(() => props.meta.fs.used / props.meta.fs.total);
|
|
||||||
const total = $computed(() => props.meta.fs.total);
|
|
||||||
const used = $computed(() => props.meta.fs.used);
|
|
||||||
const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.zbwaqsat {
|
|
||||||
display: flex;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .pie {
|
|
||||||
height: 82px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,135 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkContainer
|
|
||||||
:show-header="widgetProps.showHeader"
|
|
||||||
:naked="widgetProps.transparent"
|
|
||||||
>
|
|
||||||
<template #header
|
|
||||||
><i class="ph-hard-drives ph-bold ph-lg"></i
|
|
||||||
>{{ i18n.ts._widgets.serverMetric }}</template
|
|
||||||
>
|
|
||||||
<template #func
|
|
||||||
><button class="_button" @click="toggleView()">
|
|
||||||
<i class="ph-sort-ascending ph-bold ph-lg"></i></button
|
|
||||||
></template>
|
|
||||||
|
|
||||||
<div v-if="meta" class="mkw-serverMetric">
|
|
||||||
<XCpuMemory
|
|
||||||
v-if="widgetProps.view === 0"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
<XNet
|
|
||||||
v-else-if="widgetProps.view === 1"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
<XCpu
|
|
||||||
v-else-if="widgetProps.view === 2"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
<XMemory
|
|
||||||
v-else-if="widgetProps.view === 3"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
<XDisk
|
|
||||||
v-else-if="widgetProps.view === 4"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
<XMeili
|
|
||||||
v-else-if="
|
|
||||||
instance.features.searchFilters && widgetProps.view === 5
|
|
||||||
"
|
|
||||||
:connection="connection"
|
|
||||||
:meta="meta"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MkContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onUnmounted, ref } from "vue";
|
|
||||||
import {
|
|
||||||
useWidgetPropsManager,
|
|
||||||
Widget,
|
|
||||||
WidgetComponentEmits,
|
|
||||||
WidgetComponentExpose,
|
|
||||||
WidgetComponentProps,
|
|
||||||
} from "../widget";
|
|
||||||
import XCpuMemory from "./cpu-mem.vue";
|
|
||||||
import XNet from "./net.vue";
|
|
||||||
import XCpu from "./cpu.vue";
|
|
||||||
import XMemory from "./mem.vue";
|
|
||||||
import XDisk from "./disk.vue";
|
|
||||||
import XMeili from "./meilisearch.vue";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
|
||||||
import { GetFormResultType } from "@/scripts/form";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { stream } from "@/stream";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { instance } from "@/instance";
|
|
||||||
|
|
||||||
const name = "serverMetric";
|
|
||||||
|
|
||||||
const widgetPropsDef = {
|
|
||||||
showHeader: {
|
|
||||||
type: "boolean" as const,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
transparent: {
|
|
||||||
type: "boolean" as const,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
type: "number" as const,
|
|
||||||
default: 0,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
|
||||||
|
|
||||||
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
|
||||||
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
|
||||||
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
|
||||||
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
|
|
||||||
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
|
|
||||||
|
|
||||||
const { widgetProps, configure, save } = useWidgetPropsManager(
|
|
||||||
name,
|
|
||||||
widgetPropsDef,
|
|
||||||
props,
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const meta = ref(null);
|
|
||||||
|
|
||||||
os.api("server-info", {}).then((res) => {
|
|
||||||
meta.value = res;
|
|
||||||
});
|
|
||||||
|
|
||||||
const toggleView = () => {
|
|
||||||
if (
|
|
||||||
(widgetProps.view === 5 && instance.features.searchFilters) ||
|
|
||||||
(widgetProps.view === 4 && !instance.features.searchFilters)
|
|
||||||
) {
|
|
||||||
widgetProps.view = 0;
|
|
||||||
} else {
|
|
||||||
widgetProps.view++;
|
|
||||||
}
|
|
||||||
save();
|
|
||||||
};
|
|
||||||
|
|
||||||
const connection = stream.useChannel("serverStats");
|
|
||||||
onUnmounted(() => {
|
|
||||||
connection.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
|
||||||
name,
|
|
||||||
configure,
|
|
||||||
id: props.widget ? props.widget.id : null,
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,85 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="verusivbr">
|
|
||||||
<XPie
|
|
||||||
v-tooltip="i18n.ts.meiliIndexCount"
|
|
||||||
class="pie"
|
|
||||||
:value="progress"
|
|
||||||
:reverse="true"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
|
|
||||||
<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
|
|
||||||
<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(totalSize, 1) }}</p>
|
|
||||||
<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ indexCount }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onBeforeUnmount, onMounted } from "vue";
|
|
||||||
import bytes from "@/filters/bytes";
|
|
||||||
import XPie from "./pie.vue";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
connection: any;
|
|
||||||
meta: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let progress: number = $ref(0);
|
|
||||||
let serverStats = $ref(null);
|
|
||||||
let totalSize: number = $ref(0);
|
|
||||||
let indexCount: number = $ref(0);
|
|
||||||
let available: string = $ref("unavailable");
|
|
||||||
|
|
||||||
function onStats(stats) {
|
|
||||||
totalSize = stats.meilisearch.size;
|
|
||||||
indexCount = stats.meilisearch.indexed_count;
|
|
||||||
available = stats.meilisearch.health;
|
|
||||||
progress = indexCount / serverStats.notesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
os.api("stats", {}).then((res) => {
|
|
||||||
serverStats = res;
|
|
||||||
});
|
|
||||||
props.connection.on("stats", onStats);
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
props.connection.off("stats", onStats);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.verusivbr {
|
|
||||||
display: flex;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .pie {
|
|
||||||
height: 82px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,73 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="zlxnikvl">
|
|
||||||
<XPie class="pie" :value="usage" />
|
|
||||||
<div>
|
|
||||||
<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
|
|
||||||
<p>Total: {{ bytes(total, 1) }}</p>
|
|
||||||
<p>Used: {{ bytes(used, 1) }}</p>
|
|
||||||
<p>Free: {{ bytes(free, 1) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onBeforeUnmount } from "vue";
|
|
||||||
import XPie from "./pie.vue";
|
|
||||||
import bytes from "@/filters/bytes";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
connection: any;
|
|
||||||
meta: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let usage: number = $ref(0);
|
|
||||||
let total: number = $ref(0);
|
|
||||||
let used: number = $ref(0);
|
|
||||||
let free: number = $ref(0);
|
|
||||||
|
|
||||||
function onStats(stats) {
|
|
||||||
usage = stats.mem.active / props.meta.mem.total;
|
|
||||||
total = props.meta.mem.total;
|
|
||||||
used = stats.mem.active;
|
|
||||||
free = total - used;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.connection.on("stats", onStats);
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
props.connection.off("stats", onStats);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.zlxnikvl {
|
|
||||||
display: flex;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .pie {
|
|
||||||
height: 82px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
> p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,153 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="oxxrhrto">
|
|
||||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
|
||||||
<polygon
|
|
||||||
:points="inPolygonPoints"
|
|
||||||
fill="#f6c177"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
/>
|
|
||||||
<polyline
|
|
||||||
:points="inPolylinePoints"
|
|
||||||
fill="none"
|
|
||||||
stroke="#f6c177"
|
|
||||||
stroke-width="1"
|
|
||||||
/>
|
|
||||||
<circle :cx="inHeadX" :cy="inHeadY" r="1.5" fill="#f6c177" />
|
|
||||||
<text x="1" y="5">
|
|
||||||
NET rx
|
|
||||||
<tspan>{{ bytes(inRecent) }}</tspan>
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
|
|
||||||
<polygon
|
|
||||||
:points="outPolygonPoints"
|
|
||||||
fill="#31748f"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
/>
|
|
||||||
<polyline
|
|
||||||
:points="outPolylinePoints"
|
|
||||||
fill="none"
|
|
||||||
stroke="#31748f"
|
|
||||||
stroke-width="1"
|
|
||||||
/>
|
|
||||||
<circle :cx="outHeadX" :cy="outHeadY" r="1.5" fill="#31748f" />
|
|
||||||
<text x="1" y="5">
|
|
||||||
NET tx
|
|
||||||
<tspan>{{ bytes(outRecent) }}</tspan>
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, onBeforeUnmount } from "vue";
|
|
||||||
import bytes from "@/filters/bytes";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
connection: any;
|
|
||||||
meta: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let viewBoxX: number = $ref(50);
|
|
||||||
let viewBoxY: number = $ref(30);
|
|
||||||
let stats: any[] = $ref([]);
|
|
||||||
let inPolylinePoints: string = $ref("");
|
|
||||||
let outPolylinePoints: string = $ref("");
|
|
||||||
let inPolygonPoints: string = $ref("");
|
|
||||||
let outPolygonPoints: string = $ref("");
|
|
||||||
let inHeadX: any = $ref(null);
|
|
||||||
let inHeadY: any = $ref(null);
|
|
||||||
let outHeadX: any = $ref(null);
|
|
||||||
let outHeadY: any = $ref(null);
|
|
||||||
let inRecent: number = $ref(0);
|
|
||||||
let outRecent: number = $ref(0);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.connection.on("stats", onStats);
|
|
||||||
props.connection.on("statsLog", onStatsLog);
|
|
||||||
props.connection.send("requestLog", {
|
|
||||||
id: Math.random().toString().substr(2, 8),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
props.connection.off("stats", onStats);
|
|
||||||
props.connection.off("statsLog", onStatsLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
function onStats(connStats) {
|
|
||||||
stats.push(connStats);
|
|
||||||
if (stats.length > 50) stats.shift();
|
|
||||||
|
|
||||||
const inPeak = Math.max(1024 * 64, Math.max(...stats.map((s) => s.net.rx)));
|
|
||||||
const outPeak = Math.max(
|
|
||||||
1024 * 64,
|
|
||||||
Math.max(...stats.map((s) => s.net.tx))
|
|
||||||
);
|
|
||||||
|
|
||||||
let inPolylinePointsStats = stats.map((s, i) => [
|
|
||||||
viewBoxX - (stats.length - 1 - i),
|
|
||||||
(1 - s.net.rx / inPeak) * viewBoxY,
|
|
||||||
]);
|
|
||||||
let outPolylinePointsStats = stats.map((s, i) => [
|
|
||||||
viewBoxX - (stats.length - 1 - i),
|
|
||||||
(1 - s.net.tx / outPeak) * viewBoxY,
|
|
||||||
]);
|
|
||||||
inPolylinePoints = inPolylinePointsStats
|
|
||||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
|
||||||
.join(" ");
|
|
||||||
outPolylinePoints = outPolylinePointsStats
|
|
||||||
.map((xy) => `${xy[0]},${xy[1]}`)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
inPolygonPoints = `${
|
|
||||||
viewBoxX - (stats.length - 1)
|
|
||||||
},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
|
||||||
outPolygonPoints = `${
|
|
||||||
viewBoxX - (stats.length - 1)
|
|
||||||
},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
|
||||||
|
|
||||||
inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
|
|
||||||
inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
|
|
||||||
outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
|
|
||||||
outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
|
|
||||||
|
|
||||||
inRecent = connStats.net.rx;
|
|
||||||
outRecent = connStats.net.tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStatsLog(statsLog) {
|
|
||||||
for (const revStats of [...statsLog].reverse()) {
|
|
||||||
onStats(revStats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.oxxrhrto {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> svg {
|
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> text {
|
|
||||||
font-size: 5px;
|
|
||||||
fill: currentColor;
|
|
||||||
|
|
||||||
> tspan {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,64 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none">
|
|
||||||
<circle
|
|
||||||
:r="r"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="0.1"
|
|
||||||
stroke="rgba(0, 0, 0, 0.05)"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
:r="r"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
:stroke-dasharray="Math.PI * (r * 2)"
|
|
||||||
:stroke-dashoffset="strokeDashoffset"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="0.1"
|
|
||||||
:stroke="color"
|
|
||||||
/>
|
|
||||||
<text x="50%" y="50%" dy="0.05" text-anchor="middle">
|
|
||||||
{{ (value * 100).toFixed(0) }}%
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
value: number;
|
|
||||||
reverse?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const r = 0.45;
|
|
||||||
|
|
||||||
const color = $computed(
|
|
||||||
() =>
|
|
||||||
`hsl(${
|
|
||||||
props.reverse ? props.value * 180 : 180 - props.value * 180
|
|
||||||
}, 80%, 70%)`
|
|
||||||
);
|
|
||||||
const strokeDashoffset = $computed(
|
|
||||||
() => (1 - props.value) * (Math.PI * (r * 2))
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.hsalcinq {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
> circle {
|
|
||||||
transform-origin: center;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
transition: stroke-dashoffset 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
> text {
|
|
||||||
font-size: 0.15px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -122,9 +122,6 @@ importers:
|
||||||
'@rollup/pluginutils':
|
'@rollup/pluginutils':
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
'@syuilo/aiscript':
|
|
||||||
specifier: 0.11.1
|
|
||||||
version: 0.11.1
|
|
||||||
'@types/escape-regexp':
|
'@types/escape-regexp':
|
||||||
specifier: 0.0.1
|
specifier: 0.0.1
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
@ -353,9 +350,6 @@ importers:
|
||||||
vue-plyr:
|
vue-plyr:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
vue-prism-editor:
|
|
||||||
specifier: 2.0.0-alpha.2
|
|
||||||
version: 2.0.0-alpha.2(vue@3.3.4)
|
|
||||||
vue3-otp-input:
|
vue3-otp-input:
|
||||||
specifier: ^0.4.1
|
specifier: ^0.4.1
|
||||||
version: 0.4.1(vue@3.3.4)
|
version: 0.4.1(vue@3.3.4)
|
||||||
|
@ -1010,7 +1004,7 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@swc/core': ^1.2.66
|
'@swc/core': ^1.2.66
|
||||||
chokidar: ^3.3.1
|
chokidar: ^3.5.1
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
chokidar:
|
chokidar:
|
||||||
optional: true
|
optional: true
|
||||||
|
@ -1147,16 +1141,6 @@ packages:
|
||||||
/@swc/wasm@1.2.130:
|
/@swc/wasm@1.2.130:
|
||||||
resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==}
|
resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==}
|
||||||
|
|
||||||
/@syuilo/aiscript@0.11.1:
|
|
||||||
resolution: {integrity: sha512-chwOIA3yLUKvOB0G611hjLArKTeOWNmTm3lHERSaDW1d+dS6do56naX6Lkwy2UpnwWC0qzeNSgg35elk6t2gZg==}
|
|
||||||
dependencies:
|
|
||||||
autobind-decorator: 2.4.0
|
|
||||||
chalk: 4.0.0
|
|
||||||
seedrandom: 3.0.5
|
|
||||||
stringz: 2.1.0
|
|
||||||
uuid: 7.0.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@szmarczak/http-timer@4.0.6:
|
/@szmarczak/http-timer@4.0.6:
|
||||||
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -2328,14 +2312,6 @@ packages:
|
||||||
supports-color: 5.5.0
|
supports-color: 5.5.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chalk@4.0.0:
|
|
||||||
resolution: {integrity: sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 4.3.0
|
|
||||||
supports-color: 7.2.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/chalk@4.1.2:
|
/chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -7298,11 +7274,6 @@ packages:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/uuid@7.0.3:
|
|
||||||
resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==}
|
|
||||||
hasBin: true
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/uuid@8.3.2:
|
/uuid@8.3.2:
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -7462,15 +7433,6 @@ packages:
|
||||||
vue: 2.7.14
|
vue: 2.7.14
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vue-prism-editor@2.0.0-alpha.2(vue@3.3.4):
|
|
||||||
resolution: {integrity: sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.0.0
|
|
||||||
dependencies:
|
|
||||||
vue: 3.3.4
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/vue3-otp-input@0.4.1(vue@3.3.4):
|
/vue3-otp-input@0.4.1(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==}
|
resolution: {integrity: sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==}
|
||||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||||
|
|
|
@ -5,17 +5,46 @@ pub mod note;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
use crate::endpoints::ErrorKind;
|
|
||||||
use crate::util_types::U64Range;
|
use crate::util_types::U64Range;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
pub(crate) mod packed_time {
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub(crate) fn serialize<S>(value: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&value.timestamp_millis().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(DateTime::<Utc>::from_utc(
|
||||||
|
NaiveDateTime::from_timestamp_millis(
|
||||||
|
String::deserialize(deserializer)?
|
||||||
|
.parse::<i64>()
|
||||||
|
.map_err(Error::custom)?,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| Error::custom("millisecond value out of range"))?,
|
||||||
|
Utc,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct RangeFilter {
|
pub struct RangeFilter {
|
||||||
|
#[serde(with = "packed_time")]
|
||||||
pub time_start: DateTime<Utc>,
|
pub time_start: DateTime<Utc>,
|
||||||
|
#[serde(with = "packed_time")]
|
||||||
pub time_end: DateTime<Utc>,
|
pub time_end: DateTime<Utc>,
|
||||||
pub id_start: String,
|
pub id_start: String,
|
||||||
pub id_end: String,
|
pub id_end: String,
|
||||||
|
@ -24,6 +53,7 @@ pub struct RangeFilter {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct StartFilter {
|
pub struct StartFilter {
|
||||||
|
#[serde(with = "packed_time")]
|
||||||
pub time_start: DateTime<Utc>,
|
pub time_start: DateTime<Utc>,
|
||||||
pub id_start: String,
|
pub id_start: String,
|
||||||
}
|
}
|
||||||
|
@ -31,6 +61,7 @@ pub struct StartFilter {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct EndFilter {
|
pub struct EndFilter {
|
||||||
|
#[serde(with = "packed_time")]
|
||||||
pub time_end: DateTime<Utc>,
|
pub time_end: DateTime<Utc>,
|
||||||
pub id_end: String,
|
pub id_end: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,6 @@ pub struct UserProfileExt {
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserProfilePinsEx {
|
pub struct UserProfilePinsEx {
|
||||||
pub pinned_notes: Vec<PackNoteMaybeFull>,
|
pub pinned_notes: Vec<PackNoteMaybeFull>,
|
||||||
// pub pinned_page: Option<Page>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
|
Loading…
Reference in New Issue