Compare commits
8 Commits
a34829c63d
...
fdfd3163b1
Author | SHA1 | Date |
---|---|---|
Natty | fdfd3163b1 | |
Natty | e7812a816b | |
Natty | 0ad015cd68 | |
Natty | 6908a2f350 | |
Natty | a5ab2acca0 | |
Natty | c8627a996c | |
Natty | 2c3d675392 | |
Natty | 5a8dc04915 |
|
@ -535,7 +535,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ck"
|
name = "ck"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -835,7 +835,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ext_calckey_model_migration"
|
name = "ext_calckey_model_migration"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1446,7 +1446,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar"
|
name = "magnetar"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"cached",
|
"cached",
|
||||||
|
@ -1482,11 +1482,12 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_calckey_fe"
|
name = "magnetar_calckey_fe"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1510,7 +1511,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_calckey_model"
|
name = "magnetar_calckey_model"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"ck",
|
"ck",
|
||||||
|
@ -1535,19 +1536,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_common"
|
name = "magnetar_common"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"idna",
|
||||||
"magnetar_core",
|
"magnetar_core",
|
||||||
"magnetar_sdk",
|
"magnetar_sdk",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml 0.8.1",
|
"toml 0.8.1",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_core"
|
name = "magnetar_core"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1555,7 +1558,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_mmm_parser"
|
name = "magnetar_mmm_parser"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"either",
|
"either",
|
||||||
|
@ -1571,7 +1574,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_nodeinfo"
|
name = "magnetar_nodeinfo"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1579,7 +1582,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_sdk"
|
name = "magnetar_sdk"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"http",
|
"http",
|
||||||
|
@ -1593,7 +1596,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_sdk_macros"
|
name = "magnetar_sdk_macros"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.28",
|
"syn 2.0.28",
|
||||||
|
@ -1601,7 +1604,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_webfinger"
|
name = "magnetar_webfinger"
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"magnetar_core",
|
"magnetar_core",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -3670,9 +3673,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
|
|
|
@ -19,7 +19,7 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.1-alpha"
|
version = "0.3.0-alpha"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
@ -88,6 +88,7 @@ hyper = { workspace = true, features = ["full"] }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
tower = { workspace = true }
|
tower = { workspace = true }
|
||||||
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
|
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
|
||||||
|
url = { workspace = true }
|
||||||
|
|
||||||
idna = { workspace = true }
|
idna = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,19 @@
|
||||||
# Environment variable: MAG_C_BIND_ADDR
|
# Environment variable: MAG_C_BIND_ADDR
|
||||||
# networking.bind_addr = "::"
|
# networking.bind_addr = "::"
|
||||||
|
|
||||||
|
# [Optional]
|
||||||
|
# The URL of a media proxy
|
||||||
|
# Default: null
|
||||||
|
# Environment variable: MAG_C_MEDIA_PROXY
|
||||||
|
# networking.media_proxy = ""
|
||||||
|
|
||||||
|
# [Optional]
|
||||||
|
# Whether to proxy remote files through this instance
|
||||||
|
# Default: false
|
||||||
|
# Environment variable: MAG_C_PROXY_REMOTE_FILES
|
||||||
|
# networking.proxy_remote_files = false
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------[ CALCKEY FRONTEND ]----------------------------
|
# -----------------------------[ CALCKEY FRONTEND ]----------------------------
|
||||||
|
|
||||||
# [Optional]
|
# [Optional]
|
||||||
|
|
|
@ -61,6 +61,7 @@ impl CalckeyModel {
|
||||||
.sqlx_logging_level(LevelFilter::Debug)
|
.sqlx_logging_level(LevelFilter::Debug)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
|
info!("Attempting database connection...");
|
||||||
Ok(CalckeyModel(sea_orm::Database::connect(opt).await?))
|
Ok(CalckeyModel(sea_orm::Database::connect(opt).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +225,18 @@ impl CalckeyModel {
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_instance(
|
||||||
|
&self,
|
||||||
|
host: &str,
|
||||||
|
) -> Result<Option<instance::Model>, CalckeyDbError> {
|
||||||
|
let instance = instance::Entity::find()
|
||||||
|
.filter(instance::Column::Host.eq(host))
|
||||||
|
.one(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_instance_meta(&self) -> Result<meta::Model, CalckeyDbError> {
|
pub async fn get_instance_meta(&self) -> Result<meta::Model, CalckeyDbError> {
|
||||||
let txn = self.0.begin().await?;
|
let txn = self.0.begin().await?;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ use sea_orm::{
|
||||||
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use ck::{drive_file, note, note_reaction, user};
|
use ck::{drive_file, note, note_reaction, user};
|
||||||
use magnetar_sdk::types::RangeFilter;
|
use magnetar_sdk::types::RangeFilter;
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/cli": "^0.1.62",
|
"@swc/cli": "^0.1.62",
|
||||||
"@swc/core": "^1.3.62",
|
"@swc/core": "^1.3.62",
|
||||||
"@types/node": "20.3.1",
|
"@types/node": "20.8.10",
|
||||||
"ts-node": "10.4.0",
|
"ts-node": "10.4.0",
|
||||||
"tsd": "^0.28.1",
|
"tsd": "^0.28.1",
|
||||||
"typescript": "5.1.3"
|
"typescript": "5.1.3"
|
||||||
|
|
|
@ -15,6 +15,8 @@ export type UserLite = {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
avatarBlurhash: string;
|
avatarBlurhash: string;
|
||||||
alsoKnownAs: string[];
|
alsoKnownAs: string[];
|
||||||
|
isCat?: boolean;
|
||||||
|
isBot?: boolean;
|
||||||
movedToUri: any;
|
movedToUri: any;
|
||||||
emojis: {
|
emojis: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -0,0 +1,937 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:aria-label="accessibleLabel"
|
||||||
|
v-if="!muted.muted"
|
||||||
|
v-show="!isDeleted"
|
||||||
|
ref="el"
|
||||||
|
v-hotkey="keymap"
|
||||||
|
v-size="{ max: [500, 350] }"
|
||||||
|
class="tkcbzcuz note-container"
|
||||||
|
:tabindex="!isDeleted ? '-1' : null"
|
||||||
|
:class="{ renote: isRenote }"
|
||||||
|
:id="appearNote.id"
|
||||||
|
>
|
||||||
|
<MkNoteSub
|
||||||
|
v-if="appearNote.parent_note && !detailedView && !collapsedReply"
|
||||||
|
:note="appearNote.parent_note"
|
||||||
|
class="reply-to"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="!detailedView"
|
||||||
|
class="note-context"
|
||||||
|
@click="noteClick"
|
||||||
|
:class="{
|
||||||
|
collapsedReply: collapsedReply && appearNote.parent_note,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="line"></div>
|
||||||
|
<div v-if="appearNote._prId_" class="info">
|
||||||
|
<i class="ph-megaphone-simple-bold ph-lg"></i>
|
||||||
|
{{ i18n.ts.promotion
|
||||||
|
}}<button class="_textButton hide" @click.stop="readPromo()">
|
||||||
|
{{ i18n.ts.hideThisNote }}
|
||||||
|
<i class="ph-x ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="appearNote._featuredId_" class="info">
|
||||||
|
<i class="ph-lightning ph-bold ph-lg"></i>
|
||||||
|
{{ i18n.ts.featured }}
|
||||||
|
</div>
|
||||||
|
<div v-if="pinned" class="info">
|
||||||
|
<i class="ph-push-pin ph-bold ph-lg"></i
|
||||||
|
>{{ i18n.ts.pinnedNote }}
|
||||||
|
</div>
|
||||||
|
<div v-if="isRenote" class="renote">
|
||||||
|
<i class="ph-repeat ph-bold ph-lg"></i>
|
||||||
|
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||||
|
<template #user>
|
||||||
|
<MkA
|
||||||
|
v-user-preview="note.user.id"
|
||||||
|
class="name"
|
||||||
|
:to="userPage(note.user)"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<MkUserName :user="note.user" />
|
||||||
|
</MkA>
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
|
<div class="info">
|
||||||
|
<button
|
||||||
|
ref="renoteTime"
|
||||||
|
class="_button time"
|
||||||
|
@click.stop="showRenoteMenu()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
v-if="isMyRenote"
|
||||||
|
class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"
|
||||||
|
></i>
|
||||||
|
<MkTime :time="note.created_at" />
|
||||||
|
</button>
|
||||||
|
<MkVisibility :note="note" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="collapsedReply && appearNote.parent_note" class="info">
|
||||||
|
<MkAvatar class="avatar" :user="appearNote.parent_note.user" />
|
||||||
|
<MkUserName
|
||||||
|
class="username"
|
||||||
|
:user="appearNote.parent_note.user"
|
||||||
|
></MkUserName>
|
||||||
|
<Mfm
|
||||||
|
class="summary"
|
||||||
|
:text="getNoteSummary(appearNote.parent_note)"
|
||||||
|
:plain="true"
|
||||||
|
:nowrap="true"
|
||||||
|
:custom-emojis="note.emojis"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<article
|
||||||
|
class="article"
|
||||||
|
@contextmenu.stop="onContextmenu"
|
||||||
|
@click="noteClick"
|
||||||
|
:style="{
|
||||||
|
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="main">
|
||||||
|
<div class="header-container">
|
||||||
|
<MkAvatar class="avatar" :user="appearNote.user" />
|
||||||
|
<XNoteHeader class="header" :note="appearNote" />
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<MkSubNoteContent
|
||||||
|
class="text"
|
||||||
|
:note="appearNote"
|
||||||
|
:detailed="true"
|
||||||
|
:detailedView="detailedView"
|
||||||
|
:parentId="appearNote.id"
|
||||||
|
@push="(e) => router.push(notePage(e))"
|
||||||
|
@focusfooter="footerEl.focus()"
|
||||||
|
@expanded="(e) => setPostExpanded(e)"
|
||||||
|
></MkSubNoteContent>
|
||||||
|
<div v-if="translating || translation" class="translation">
|
||||||
|
<MkLoading v-if="translating" mini />
|
||||||
|
<div v-else class="translated">
|
||||||
|
<b
|
||||||
|
>{{
|
||||||
|
i18n.t("translatedFrom", {
|
||||||
|
x: translation.sourceLang,
|
||||||
|
})
|
||||||
|
}}:
|
||||||
|
</b>
|
||||||
|
<Mfm
|
||||||
|
:text="translation.text"
|
||||||
|
:author="appearNote.user"
|
||||||
|
:i="$i"
|
||||||
|
:custom-emojis="appearNote.emojis"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="detailedView" class="info">
|
||||||
|
<MkA class="created-at" :to="notePage(appearNote)">
|
||||||
|
<MkTime :time="appearNote.created_at" mode="absolute" />
|
||||||
|
</MkA>
|
||||||
|
</div>
|
||||||
|
<footer ref="footerEl" class="footer" @click.stop tabindex="-1">
|
||||||
|
<XReactionsViewer
|
||||||
|
v-if="enableEmojiReactions"
|
||||||
|
ref="reactionsViewer"
|
||||||
|
:note="appearNote"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.reply"
|
||||||
|
class="button _button"
|
||||||
|
@click="reply()"
|
||||||
|
>
|
||||||
|
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||||
|
<template
|
||||||
|
v-if="appearNote.reply_count > 0 && !detailedView"
|
||||||
|
>
|
||||||
|
<p class="count">{{ appearNote.reply_count }}</p>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<XRenoteButton
|
||||||
|
ref="renoteButton"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
:count="appearNote.renote_count"
|
||||||
|
:detailedView="detailedView"
|
||||||
|
/>
|
||||||
|
<XStarButtonNoEmoji
|
||||||
|
v-if="!enableEmojiReactions"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
:count="magReactionCount(appearNote)"
|
||||||
|
:reacted="magHasReacted(appearNote)"
|
||||||
|
/>
|
||||||
|
<XStarButton
|
||||||
|
v-if="
|
||||||
|
enableEmojiReactions && !magHasReacted(appearNote)
|
||||||
|
"
|
||||||
|
ref="starButton"
|
||||||
|
class="button"
|
||||||
|
:note="appearNote"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="
|
||||||
|
enableEmojiReactions && !magHasReacted(appearNote)
|
||||||
|
"
|
||||||
|
ref="reactButton"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||||
|
class="button _button"
|
||||||
|
@click="react()"
|
||||||
|
>
|
||||||
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="enableEmojiReactions && magHasReacted(appearNote)"
|
||||||
|
ref="reactButton"
|
||||||
|
class="button _button reacted"
|
||||||
|
@click="undoReact(appearNote)"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
||||||
|
>
|
||||||
|
<i class="ph-minus ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<XQuoteButton class="button" :note="appearNote" />
|
||||||
|
<button
|
||||||
|
ref="menuButton"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.more"
|
||||||
|
class="button _button"
|
||||||
|
@click="menu()"
|
||||||
|
>
|
||||||
|
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<button v-else class="muted _button" @click="muted.muted = false">
|
||||||
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
|
<template #name>
|
||||||
|
<MkA
|
||||||
|
v-user-preview="note.user.id"
|
||||||
|
class="name"
|
||||||
|
:to="userPage(note.user)"
|
||||||
|
>
|
||||||
|
<MkUserName :user="note.user" />
|
||||||
|
</MkA>
|
||||||
|
</template>
|
||||||
|
<template #reason>
|
||||||
|
<b class="_blur_text">{{ muted.matched.join(", ") }}</b>
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
||||||
|
import * as mfm from "mfm-js";
|
||||||
|
import type * as misskey from "calckey-js";
|
||||||
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
|
import { url } from "@/config";
|
||||||
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
import { focusNext, focusPrev } from "@/scripts/focus";
|
||||||
|
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
||||||
|
import { useRouter } from "@/router";
|
||||||
|
import { userPage } from "@/filters/user";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import { defaultStore, noteViewInterruptors } from "@/store";
|
||||||
|
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
|
import { $i } from "@/account";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||||
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
|
import { notePage } from "@/filters/note";
|
||||||
|
import { getNoteSummary } from "@/scripts/get-note-summary";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magEffectiveNote,
|
||||||
|
magHasReacted,
|
||||||
|
magIsRenote,
|
||||||
|
magReactionCount,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
note: packed.PackNoteMaybeFull;
|
||||||
|
pinned?: boolean;
|
||||||
|
detailedView?: boolean;
|
||||||
|
collapsedReply?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
|
||||||
|
|
||||||
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
if (what === "reply") return i18n.ts.userSaysSomethingReasonReply;
|
||||||
|
if (what === "renote") return i18n.ts.userSaysSomethingReasonRenote;
|
||||||
|
if (what === "quote") return i18n.ts.userSaysSomethingReasonQuote;
|
||||||
|
|
||||||
|
// I don't think here is reachable, but just in case
|
||||||
|
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 isRenote = magIsRenote(note);
|
||||||
|
|
||||||
|
const el = ref<HTMLElement>();
|
||||||
|
const footerEl = ref<HTMLElement>();
|
||||||
|
const menuButton = ref<HTMLElement>();
|
||||||
|
const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
|
const renoteTime = ref<HTMLElement>();
|
||||||
|
const reactButton = ref<HTMLElement>();
|
||||||
|
let appearNote = $computed(
|
||||||
|
() => magEffectiveNote(note) as packed.PackNoteMaybeFull
|
||||||
|
);
|
||||||
|
const isMyRenote = $i && $i.id === note.user.id;
|
||||||
|
const showContent = ref(false);
|
||||||
|
const isDeleted = ref(false);
|
||||||
|
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
||||||
|
const translation = ref(null);
|
||||||
|
const translating = ref(false);
|
||||||
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
|
||||||
|
const keymap = {
|
||||||
|
r: () => reply(true),
|
||||||
|
"e|a|plus": () => react(true),
|
||||||
|
q: () => renoteButton.value.renote(true),
|
||||||
|
"up|k": focusBefore,
|
||||||
|
"down|j": focusAfter,
|
||||||
|
esc: blur,
|
||||||
|
"m|o": () => menu(true),
|
||||||
|
s: () => showContent.value !== showContent.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
useNoteCapture({
|
||||||
|
rootEl: el,
|
||||||
|
note: $$(appearNote),
|
||||||
|
isDeletedRef: isDeleted,
|
||||||
|
});
|
||||||
|
|
||||||
|
function reply(viaKeyboard = false): void {
|
||||||
|
pleaseLogin();
|
||||||
|
os.post(
|
||||||
|
{
|
||||||
|
reply: appearNote,
|
||||||
|
animation: !viaKeyboard,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function react(viaKeyboard = false): void {
|
||||||
|
pleaseLogin();
|
||||||
|
blur();
|
||||||
|
reactionPicker.show(
|
||||||
|
reactButton.value,
|
||||||
|
(reaction) => {
|
||||||
|
os.api("notes/reactions/create", {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
reaction: reaction,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function undoReact(note): void {
|
||||||
|
const oldReaction = note.myReaction;
|
||||||
|
if (!oldReaction) return;
|
||||||
|
os.api("notes/reactions/delete", {
|
||||||
|
noteId: note.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
|
||||||
|
"currentClipPage",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
function onContextmenu(ev: MouseEvent): void {
|
||||||
|
const isLink = (el: HTMLElement) => {
|
||||||
|
if (el.tagName === "A") return true;
|
||||||
|
// The Audio element's context menu is the browser default, such as for selecting playback speed.
|
||||||
|
if (el.tagName === "AUDIO") return true;
|
||||||
|
if (el.parentElement) {
|
||||||
|
return isLink(el.parentElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (isLink(ev.target)) return;
|
||||||
|
if (window.getSelection()?.toString() !== "") return;
|
||||||
|
|
||||||
|
if (defaultStore.state.useReactionPickerForContextMenu) {
|
||||||
|
ev.preventDefault();
|
||||||
|
react();
|
||||||
|
} else {
|
||||||
|
os.contextMenu(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
text: notePage(appearNote),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "ph-browser ph-bold ph-lg",
|
||||||
|
text: i18n.ts.openInWindow,
|
||||||
|
action: () => {
|
||||||
|
os.pageWindow(notePage(appearNote));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notePage(appearNote) != location.pathname
|
||||||
|
? {
|
||||||
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
|
text: i18n.ts.showInPage,
|
||||||
|
action: () => {
|
||||||
|
router.push(notePage(appearNote), "forcePage");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
type: "a",
|
||||||
|
icon: "ph-arrow-square-out ph-bold ph-lg",
|
||||||
|
text: i18n.ts.openInNewTab,
|
||||||
|
href: notePage(appearNote),
|
||||||
|
target: "_blank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "ph-link-simple ph-bold ph-lg",
|
||||||
|
text: i18n.ts.copyLink,
|
||||||
|
action: () => {
|
||||||
|
copyToClipboard(`${url}${notePage(appearNote)}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appearNote.user.host != null
|
||||||
|
? {
|
||||||
|
type: "a",
|
||||||
|
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
||||||
|
text: i18n.ts.showOnRemote,
|
||||||
|
href: appearNote.url ?? appearNote.uri ?? "",
|
||||||
|
target: "_blank",
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
],
|
||||||
|
ev
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function menu(viaKeyboard = false): void {
|
||||||
|
os.popupMenu(
|
||||||
|
getNoteMenu({
|
||||||
|
note: note,
|
||||||
|
translating,
|
||||||
|
translation,
|
||||||
|
menuButton,
|
||||||
|
isDeleted,
|
||||||
|
currentClipPage,
|
||||||
|
}),
|
||||||
|
menuButton.value,
|
||||||
|
{
|
||||||
|
viaKeyboard,
|
||||||
|
}
|
||||||
|
).then(focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRenoteMenu(viaKeyboard = false): void {
|
||||||
|
if (!isMyRenote) return;
|
||||||
|
os.popupMenu(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: i18n.ts.unrenote,
|
||||||
|
icon: "ph-trash ph-bold ph-lg",
|
||||||
|
danger: true,
|
||||||
|
action: () => {
|
||||||
|
os.api("notes/delete", {
|
||||||
|
noteId: note.id,
|
||||||
|
});
|
||||||
|
isDeleted.value = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
renoteTime.value,
|
||||||
|
{
|
||||||
|
viaKeyboard: viaKeyboard,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
el.value?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function blur() {
|
||||||
|
el.value?.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusBefore() {
|
||||||
|
focusPrev(el.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusAfter() {
|
||||||
|
focusNext(el.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollIntoView() {
|
||||||
|
el.value?.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteClick(e) {
|
||||||
|
if (
|
||||||
|
document.getSelection()?.type === "Range" ||
|
||||||
|
props.detailedView ||
|
||||||
|
!expandOnNoteClick
|
||||||
|
) {
|
||||||
|
e.stopPropagation();
|
||||||
|
} else {
|
||||||
|
router.push(notePage(appearNote));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPromo() {
|
||||||
|
os.api("promo/read", {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
});
|
||||||
|
isDeleted.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let postIsExpanded = ref(false);
|
||||||
|
|
||||||
|
function setPostExpanded(val: boolean) {
|
||||||
|
postIsExpanded.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessibleLabel = computed(() => {
|
||||||
|
let label = `${appearNote.user.username}; `;
|
||||||
|
if (appearNote.renoted_note) {
|
||||||
|
label += `${i18n.t("renoted")} ${
|
||||||
|
appearNote.renoted_note.user.username
|
||||||
|
}; `;
|
||||||
|
if (appearNote.renoted_note.cw) {
|
||||||
|
label += `${i18n.t("cw")}: ${appearNote.renoted_note.cw}; `;
|
||||||
|
if (postIsExpanded.value) {
|
||||||
|
label += `${appearNote.renoted_note.text}; `;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label += `${appearNote.renoted_note.text}; `;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (appearNote.cw) {
|
||||||
|
label += `${i18n.t("cw")}: ${appearNote.cw}; `;
|
||||||
|
if (postIsExpanded.value) {
|
||||||
|
label += `${appearNote.text}; `;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label += `${appearNote.text}; `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const date = new Date(appearNote.created_at);
|
||||||
|
label += `${date.toLocaleTimeString()}`;
|
||||||
|
return label;
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus,
|
||||||
|
blur,
|
||||||
|
scrollIntoView,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tkcbzcuz {
|
||||||
|
position: relative;
|
||||||
|
transition: box-shadow 0.1s ease;
|
||||||
|
font-size: 1.05em;
|
||||||
|
overflow: clip;
|
||||||
|
contain: content;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
|
||||||
|
// 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう
|
||||||
|
// ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、
|
||||||
|
// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
|
||||||
|
// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
|
||||||
|
//content-visibility: auto;
|
||||||
|
//contain-intrinsic-size: 0 128px;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
border: solid 1px var(--focus);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .article > .main {
|
||||||
|
&:hover,
|
||||||
|
&:focus-within {
|
||||||
|
:deep(.footer .button) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .reply-to {
|
||||||
|
& + .note-context {
|
||||||
|
.line::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-left: 2px solid currentColor;
|
||||||
|
margin-left: calc((var(--avatarSize) / 2) - 1px);
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-context {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 32px 0 32px;
|
||||||
|
display: flex;
|
||||||
|
z-index: 1;
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
> :not(.line) {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
> .line {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: var(--avatarSize);
|
||||||
|
display: flex;
|
||||||
|
margin-right: 14px;
|
||||||
|
margin-top: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > i {
|
||||||
|
margin-left: -0.5px;
|
||||||
|
}
|
||||||
|
> .info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 90%;
|
||||||
|
white-space: pre;
|
||||||
|
color: #f6c177;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .hide {
|
||||||
|
margin-left: auto;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .renote {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: pre;
|
||||||
|
color: var(--renote);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .info {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.9em;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> .time {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: inherit;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
> .dropdownIcon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsedReply {
|
||||||
|
.line {
|
||||||
|
opacity: 0.25;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-left: 2px solid currentColor;
|
||||||
|
border-top: 2px solid currentColor;
|
||||||
|
margin-left: calc(var(--avatarSize) / 2 - 1px);
|
||||||
|
width: calc(var(--avatarSize) / 2 + 14px);
|
||||||
|
border-top-left-radius: calc(var(--avatarSize) / 4);
|
||||||
|
top: calc(50% - 1px);
|
||||||
|
height: calc(50% + 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
border-radius: 2em;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
background: var(--panelHighlight);
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 30%;
|
||||||
|
&::after {
|
||||||
|
content: ": ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover,
|
||||||
|
&:focus-within {
|
||||||
|
.info {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .article {
|
||||||
|
position: relative;
|
||||||
|
overflow: clip;
|
||||||
|
padding: 20px 32px 10px;
|
||||||
|
margin-top: -16px;
|
||||||
|
|
||||||
|
&:first-child,
|
||||||
|
&:nth-child(2) {
|
||||||
|
margin-top: -100px;
|
||||||
|
padding-top: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
> .avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: block;
|
||||||
|
margin: 0 14px 0 0;
|
||||||
|
width: var(--avatarSize);
|
||||||
|
height: var(--avatarSize);
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
> .header {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> .body {
|
||||||
|
margin-top: 0.7em;
|
||||||
|
> .translation {
|
||||||
|
border: solid 0.5px var(--divider);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
> .renote {
|
||||||
|
padding-top: 8px;
|
||||||
|
> * {
|
||||||
|
padding: 16px;
|
||||||
|
border: solid 1px var(--renote);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
&:hover,
|
||||||
|
&:focus-within {
|
||||||
|
background-color: var(--panelHighlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.7em;
|
||||||
|
margin-top: 16px;
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
> .footer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
pointer-events: none; // Allow clicking anything w/out pointer-events: all; to open post
|
||||||
|
margin-top: 0.4em;
|
||||||
|
> :deep(.button) {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 3.5em;
|
||||||
|
width: max-content;
|
||||||
|
min-width: max-content;
|
||||||
|
pointer-events: all;
|
||||||
|
height: auto;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
bottom: 2px;
|
||||||
|
background: var(--panel);
|
||||||
|
z-index: -1;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
&:first-of-type {
|
||||||
|
margin-left: -0.5em;
|
||||||
|
&::before {
|
||||||
|
border-radius: 100px 0 0 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
&::before {
|
||||||
|
border-radius: 0 100px 100px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: var(--fgHighlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
> i {
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .count {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 0 0 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.reacted {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .reply {
|
||||||
|
border-top: solid 0.5px var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.max-width_500px {
|
||||||
|
font-size: 0.975em;
|
||||||
|
--avatarSize: 46px;
|
||||||
|
padding-top: 6px;
|
||||||
|
> .note-context {
|
||||||
|
padding-inline: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
> :not(.line) {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
> .line {
|
||||||
|
margin-right: 10px;
|
||||||
|
&::before {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .article {
|
||||||
|
padding: 18px 16px 8px;
|
||||||
|
&:first-child,
|
||||||
|
&:nth-child(2) {
|
||||||
|
padding-top: 104px;
|
||||||
|
}
|
||||||
|
> .main > .header-container > .avatar {
|
||||||
|
margin-right: 10px;
|
||||||
|
// top: calc(14px + var(--stickyTop, 0px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.max-width_300px {
|
||||||
|
--avatarSize: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
._blur_text {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,7 +7,7 @@
|
||||||
v-size="{ max: [500, 350, 300] }"
|
v-size="{ max: [500, 350, 300] }"
|
||||||
class="lxwezrsl _block"
|
class="lxwezrsl _block"
|
||||||
:tabindex="!isDeleted ? '-1' : null"
|
:tabindex="!isDeleted ? '-1' : null"
|
||||||
:class="{ renote: isRenote }"
|
:class="{ renote: magIsRenote(note) }"
|
||||||
>
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="conversation"
|
v-if="conversation"
|
||||||
|
@ -17,38 +17,38 @@
|
||||||
:note="note"
|
:note="note"
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="note.reply" mini />
|
<MkLoading v-else-if="note.parent_note" mini />
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="note.reply"
|
v-if="note.parent_note"
|
||||||
:note="note.reply"
|
:note="note.parent_note"
|
||||||
class="reply-to"
|
class="reply-to"
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MkNote
|
<MagNote
|
||||||
ref="noteEl"
|
ref="noteEl"
|
||||||
@contextmenu.stop="onContextmenu"
|
@contextmenu.stop="onContextmenu"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:note="note"
|
:note="note"
|
||||||
detailedView
|
detailedView
|
||||||
></MkNote>
|
></MagNote>
|
||||||
|
|
||||||
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
||||||
<option value="replies">
|
<option value="replies">
|
||||||
<!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> -->
|
||||||
<span v-if="note.repliesCount > 0" class="count">{{
|
<span v-if="note.reply_count > 0" class="count">{{
|
||||||
note.repliesCount
|
note.reply_count
|
||||||
}}</span>
|
}}</span>
|
||||||
{{ i18n.ts._notification._types.reply }}
|
{{ i18n.ts._notification._types.reply }}
|
||||||
</option>
|
</option>
|
||||||
<option value="renotes" v-if="note.renoteCount > 0">
|
<option value="renotes" v-if="note.renote_count > 0">
|
||||||
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ note.renoteCount }}</span>
|
<span class="count">{{ note.renote_count }}</span>
|
||||||
{{ i18n.ts._notification._types.renote }}
|
{{ i18n.ts._notification._types.renote }}
|
||||||
</option>
|
</option>
|
||||||
<option value="reactions" v-if="reactionsCount > 0">
|
<option value="reactions" v-if="magReactionCount(note) > 0">
|
||||||
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ reactionsCount }}</span>
|
<span class="count">{{ magReactionCount(note) }}</span>
|
||||||
{{ i18n.ts.reaction }}
|
{{ i18n.ts.reaction }}
|
||||||
</option>
|
</option>
|
||||||
<option value="quotes" v-if="directQuotes?.length > 0">
|
<option value="quotes" v-if="directQuotes?.length > 0">
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
:parentId="note.id"
|
:parentId="note.id"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
<MkLoading v-else-if="tab === 'replies' && note.reply_count > 0" />
|
||||||
|
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="directQuotes && tab === 'quotes'"
|
v-if="directQuotes && tab === 'quotes'"
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
:with-chart="false"
|
:with-chart="false"
|
||||||
/>
|
/>
|
||||||
<!-- </MkPagination> -->
|
<!-- </MkPagination> -->
|
||||||
<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />
|
<MkLoading v-else-if="tab === 'renotes' && note.renote_count > 0" />
|
||||||
|
|
||||||
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
||||||
<MkA
|
<MkA
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<MkLoading v-else-if="tab === 'clips' && clips.length > 0" />
|
<MkLoading v-else-if="tab === 'clips' && clips.length > 0" />
|
||||||
|
|
||||||
<MkReactedUsers
|
<MkReactedUsers
|
||||||
v-if="tab === 'reactions' && reactionsCount > 0"
|
v-if="tab === 'reactions' && magReactionCount(note) > 0"
|
||||||
:note-id="note.id"
|
:note-id="note.id"
|
||||||
></MkReactedUsers>
|
></MkReactedUsers>
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
<template #name>
|
<template #name>
|
||||||
<MkA
|
<MkA
|
||||||
v-user-preview="note.userId"
|
v-user-preview="note.user.id"
|
||||||
class="name"
|
class="name"
|
||||||
:to="userPage(note.user)"
|
:to="userPage(note.user)"
|
||||||
>
|
>
|
||||||
|
@ -150,22 +150,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { onMounted, onUnmounted, onUpdated, ref, toRaw } from "vue";
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import MkNote from "@/components/MkNote.vue";
|
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -178,19 +167,20 @@ import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { NoteUpdatedEvent } from "calckey-js/built/streaming.types";
|
import { NoteUpdatedEvent } from "calckey-js/built/streaming.types";
|
||||||
import appear from "@/directives/appear";
|
import { packed } from "magnetar-common";
|
||||||
|
import { magIsRenote, magReactionCount } from "@/scripts-mag/mag-util";
|
||||||
|
import MagNote from "@/components/MagNote.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let tab = $ref("replies");
|
let tab = $ref("replies");
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -205,7 +195,7 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
// plugin
|
// plugin
|
||||||
if (noteViewInterruptors.length > 0) {
|
if (noteViewInterruptors.length > 0) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let result = deepClone(note);
|
let result = structuredClone(toRaw(note));
|
||||||
for (const interruptor of noteViewInterruptors) {
|
for (const interruptor of noteViewInterruptors) {
|
||||||
result = await interruptor.handler(result);
|
result = await interruptor.handler(result);
|
||||||
}
|
}
|
||||||
|
@ -231,11 +221,6 @@ let clips = $ref();
|
||||||
let renotes = $ref();
|
let renotes = $ref();
|
||||||
let isScrolling;
|
let isScrolling;
|
||||||
|
|
||||||
const reactionsCount = Object.values(props.note.reactions).reduce(
|
|
||||||
(x, y) => x + y,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
r: () => reply(true),
|
r: () => reply(true),
|
||||||
"e|a|plus": () => react(true),
|
"e|a|plus": () => react(true),
|
||||||
|
@ -294,7 +279,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLink(ev.target)) return;
|
if (isLink(ev.target)) return;
|
||||||
if (window.getSelection().toString() !== "") return;
|
if (window.getSelection()?.toString() !== "") return;
|
||||||
|
|
||||||
if (defaultStore.state.useReactionPickerForContextMenu) {
|
if (defaultStore.state.useReactionPickerForContextMenu) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -344,7 +329,7 @@ os.api("notes/children", {
|
||||||
depth: 12,
|
depth: 12,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
res = res.reduce((acc, resNote) => {
|
res = res.reduce((acc, resNote) => {
|
||||||
if (resNote.userId == note.userId) {
|
if (resNote.userId == note.user.id) {
|
||||||
return [...acc, resNote];
|
return [...acc, resNote];
|
||||||
}
|
}
|
||||||
return [resNote, ...acc];
|
return [resNote, ...acc];
|
||||||
|
@ -357,9 +342,9 @@ os.api("notes/children", {
|
||||||
});
|
});
|
||||||
|
|
||||||
conversation = null;
|
conversation = null;
|
||||||
if (note.replyId) {
|
if (note.parent_note_id) {
|
||||||
os.api("notes/conversation", {
|
os.api("notes/conversation", {
|
||||||
noteId: note.replyId,
|
noteId: note.parent_note_id,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
conversation = res.reverse();
|
conversation = res.reverse();
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="defgtij">
|
<div class="defgtij">
|
||||||
<div v-for="user in users" :key="user.id" class="avatar-holder">
|
<div v-for="user in users" :key="user.id" class="avatar-holder">
|
||||||
<MkAvatar :user="user" :show-indicator="true" class="avatar" />
|
<MkAvatar :user="user" class="avatar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -18,10 +18,12 @@ import { length } from "stringz";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { concat } from "@/scripts/array";
|
import { concat } from "@/scripts/array";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -31,15 +33,18 @@ const emit = defineEmits<{
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement>();
|
||||||
|
|
||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
|
const attachments = magTransProperty(props.note, "attachments", "files");
|
||||||
|
const renote = magTransProperty(props.note, "renoted_note", "renote");
|
||||||
|
|
||||||
return concat([
|
return concat([
|
||||||
props.note.text
|
props.note.text
|
||||||
? [i18n.t("_cw.chars", { count: length(props.note.text) })]
|
? [i18n.t("_cw.chars", { count: length(props.note.text) })]
|
||||||
: [],
|
: [],
|
||||||
props.note.files && props.note.files.length !== 0
|
attachments && attachments.length !== 0
|
||||||
? [i18n.t("_cw.files", { count: props.note.files.length })]
|
? [i18n.t("_cw.files", { count: attachments.length })]
|
||||||
: [],
|
: [],
|
||||||
props.note.poll != null ? [i18n.ts.poll] : [],
|
props.note.poll != null ? [i18n.ts.poll] : [],
|
||||||
props.note.renote != null ? [i18n.ts.quoteAttached] : [],
|
renote != null ? [i18n.ts.quoteAttached] : [],
|
||||||
] as string[][]).join(", ");
|
] as string[][]).join(", ");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
}"
|
}"
|
||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
@click.stop="onClick"
|
@click.stop="onClick"
|
||||||
:aria-label="`${state} ${user.name || user.username}`"
|
:aria-label="`${state} ${magTransUsername(user)}`"
|
||||||
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
|
v-tooltip="full ? null : `${state} ${magTransUsername(user)}`"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="isBlocking">
|
<template v-if="isBlocking">
|
||||||
|
@ -28,13 +28,19 @@
|
||||||
><i class="ph-prohibit ph-bold ph-lg"></i>
|
><i class="ph-prohibit ph-bold ph-lg"></i>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-else-if="hasPendingFollowRequestFromYou && user.isLocked"
|
v-else-if="
|
||||||
|
hasPendingFollowRequestFromYou &&
|
||||||
|
magTransProperty(user, 'is_locked', 'isLocked')
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span>{{ (state = i18n.ts.followRequestPending) }}</span
|
<span>{{ (state = i18n.ts.followRequestPending) }}</span
|
||||||
><i class="ph-hourglass-medium ph-bold ph-lg"></i>
|
><i class="ph-hourglass-medium ph-bold ph-lg"></i>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"
|
v-else-if="
|
||||||
|
hasPendingFollowRequestFromYou &&
|
||||||
|
!magTransProperty(user, 'is_locked', 'isLocked')
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<!-- つまりリモートフォローの場合。 -->
|
<!-- つまりリモートフォローの場合。 -->
|
||||||
<span>{{ (state = i18n.ts.processing) }}</span
|
<span>{{ (state = i18n.ts.processing) }}</span
|
||||||
|
@ -44,11 +50,21 @@
|
||||||
<span>{{ (state = i18n.ts.unfollow) }}</span
|
<span>{{ (state = i18n.ts.unfollow) }}</span
|
||||||
><i class="ph-minus ph-bold ph-lg"></i>
|
><i class="ph-minus ph-bold ph-lg"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!isFollowing && user.isLocked">
|
<template
|
||||||
|
v-else-if="
|
||||||
|
!isFollowing &&
|
||||||
|
magTransProperty(user, 'is_locked', 'isLocked')
|
||||||
|
"
|
||||||
|
>
|
||||||
<span>{{ (state = i18n.ts.followRequest) }}</span
|
<span>{{ (state = i18n.ts.followRequest) }}</span
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
><i class="ph-plus ph-bold ph-lg"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!isFollowing && !user.isLocked">
|
<template
|
||||||
|
v-else-if="
|
||||||
|
!isFollowing &&
|
||||||
|
!magTransProperty(user, 'is_locked', 'isLocked')
|
||||||
|
"
|
||||||
|
>
|
||||||
<span>{{ (state = i18n.ts.follow) }}</span
|
<span>{{ (state = i18n.ts.follow) }}</span
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
><i class="ph-plus ph-bold ph-lg"></i>
|
||||||
</template>
|
</template>
|
||||||
|
@ -69,13 +85,15 @@ import { i18n } from "@/i18n";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty, magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const emit = defineEmits(["refresh"]);
|
const emit = defineEmits(["refresh"]);
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: packed.PackUserMaybeAll | Misskey.entities.User;
|
||||||
full?: boolean;
|
full?: boolean;
|
||||||
large?: boolean;
|
large?: boolean;
|
||||||
hideMenu?: boolean;
|
hideMenu?: boolean;
|
||||||
|
@ -86,24 +104,38 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isBlocking = computed(() => props.user.isBlocking);
|
const isBlocking = computed(() =>
|
||||||
|
magTransProperty(props.user, "you_block", "isBlocking")
|
||||||
|
);
|
||||||
|
|
||||||
let state = $ref(i18n.ts.processing);
|
let state = $ref(i18n.ts.processing);
|
||||||
|
|
||||||
let isFollowing = $ref(props.user.isFollowing);
|
let isFollowing = $ref(
|
||||||
|
magTransProperty(props.user, "you_follow", "isFollowing")
|
||||||
|
);
|
||||||
let hasPendingFollowRequestFromYou = $ref(
|
let hasPendingFollowRequestFromYou = $ref(
|
||||||
props.user.hasPendingFollowRequestFromYou
|
magTransProperty(
|
||||||
|
props.user,
|
||||||
|
"you_request_follow",
|
||||||
|
"hasPendingFollowRequestFromYou"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
let wait = $ref(false);
|
let wait = $ref(false);
|
||||||
const connection = stream.useChannel("main");
|
const connection = stream.useChannel("main");
|
||||||
|
|
||||||
if (props.user.isFollowing == null) {
|
if (
|
||||||
|
typeof magTransProperty(props.user, "you_follow", "isFollowing") !==
|
||||||
|
"boolean"
|
||||||
|
) {
|
||||||
os.api("users/show", {
|
os.api("users/show", {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
}).then(onFollowChange);
|
}).then(onFollowChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFollowChange(user: Misskey.entities.UserDetailed) {
|
function onFollowChange(user: Misskey.entities.User) {
|
||||||
|
if (!("isFollowing" in user && "hasPendingFollowRequestFromYou" in user))
|
||||||
|
return;
|
||||||
|
|
||||||
if (user.id === props.user.id) {
|
if (user.id === props.user.id) {
|
||||||
isFollowing = user.isFollowing;
|
isFollowing = user.isFollowing;
|
||||||
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
||||||
|
@ -124,7 +156,7 @@ async function onClick() {
|
||||||
await os.api("blocking/delete", {
|
await os.api("blocking/delete", {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
if (props.user.isMuted) {
|
if (magTransProperty(props.user, "mute", "isMuted")) {
|
||||||
await os.api("mute/delete", {
|
await os.api("mute/delete", {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
|
@ -134,7 +166,7 @@ async function onClick() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
text: i18n.t("unfollowConfirm", {
|
text: i18n.t("unfollowConfirm", {
|
||||||
name: props.user.name || props.user.username,
|
name: magTransUsername(props.user),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="hpaizdrt"
|
class="hpaizdrt"
|
||||||
v-tooltip="capitalize(instance.softwareName)"
|
v-tooltip="
|
||||||
|
capitalize(
|
||||||
|
magTransProperty(instance, 'software_name', 'softwareName') ??
|
||||||
|
'?'
|
||||||
|
)
|
||||||
|
"
|
||||||
ref="ticker"
|
ref="ticker"
|
||||||
:style="bg"
|
:style="bg"
|
||||||
>
|
>
|
||||||
|
@ -14,44 +19,59 @@
|
||||||
import { instanceName } from "@/config";
|
import { instanceName } from "@/config";
|
||||||
import { instance as Instance } from "@/instance";
|
import { instance as Instance } from "@/instance";
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
import { types } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
instance?: {
|
instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null;
|
||||||
faviconUrl?: string;
|
|
||||||
name: string;
|
|
||||||
themeColor?: string;
|
|
||||||
softwareName?: string;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let ticker = $ref<HTMLElement | null>(null);
|
let ticker = $ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
// if no instance data is given, this is for the local instance
|
// if no instance data is given, this is for the local instance
|
||||||
const instance = props.instance ?? {
|
const instance = props.instance ?? {
|
||||||
faviconUrl: Instance.iconUrl || Instance.faviconUrl || "/favicon.ico",
|
iconUrl: null,
|
||||||
|
faviconUrl: (Instance.iconUrl || Instance.faviconUrl || "/favicon.ico") as
|
||||||
|
| string
|
||||||
|
| null,
|
||||||
name: instanceName,
|
name: instanceName,
|
||||||
themeColor: (
|
themeColor: (
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
'meta[name="theme-color-orig"]'
|
'meta[name="theme-color-orig"]'
|
||||||
) as HTMLMetaElement
|
) as HTMLMetaElement
|
||||||
)?.content,
|
)?.content,
|
||||||
softwareName: Instance.softwareName || "Calckey",
|
softwareName: (Instance.softwareName || "Magnetar") as string | null,
|
||||||
|
softwareVersion: (Instance.softwareVersion || Instance.version || "") as
|
||||||
|
| string
|
||||||
|
| null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const capitalize = (s: string) => s && s[0].toUpperCase() + s.slice(1);
|
const capitalize = (s: string) => s && s[0].toUpperCase() + s.slice(1);
|
||||||
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
const computedStyle = getComputedStyle(document.documentElement);
|
||||||
const themeColor =
|
const themeColor =
|
||||||
instance.themeColor ?? computedStyle.getPropertyValue("--bg");
|
magTransProperty(instance, "theme_color", "themeColor") ??
|
||||||
|
computedStyle.getPropertyValue("--bg");
|
||||||
|
|
||||||
const bg = {
|
const bg = {
|
||||||
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}55)`,
|
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}55)`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInstanceIcon(instance): string {
|
function getInstanceIcon(
|
||||||
|
instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null
|
||||||
|
): string {
|
||||||
|
if (!instance) return "/client-assets/dummy.png";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
getProxiedImageUrlNullable(instance.iconUrl, "preview") ??
|
getProxiedImageUrlNullable(
|
||||||
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
|
magTransProperty(instance, "icon_url", "iconUrl"),
|
||||||
|
"preview"
|
||||||
|
) ??
|
||||||
|
getProxiedImageUrlNullable(
|
||||||
|
magTransProperty(instance, "favicon_url", "faviconUrl"),
|
||||||
|
"preview"
|
||||||
|
) ??
|
||||||
"/client-assets/dummy.png"
|
"/client-assets/dummy.png"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-media-banner" @click.stop>
|
<div class="mk-media-banner" @click.stop>
|
||||||
<div
|
<div
|
||||||
v-if="media.isSensitive && hide"
|
v-if="magTransProperty(media, 'sensitive', 'isSensitive') && hide"
|
||||||
class="sensitive"
|
class="sensitive"
|
||||||
@click="hide = false"
|
@click="hide = false"
|
||||||
>
|
>
|
||||||
|
@ -11,7 +11,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
media.type.startsWith('audio') && media.type !== 'audio/midi'
|
((type) => type.startsWith('audio') && type !== 'audio/midi')(
|
||||||
|
magTransProperty(media, 'mime_type', 'type')
|
||||||
|
)
|
||||||
"
|
"
|
||||||
class="audio"
|
class="audio"
|
||||||
>
|
>
|
||||||
|
@ -62,10 +64,12 @@ import type * as misskey from "calckey-js";
|
||||||
import { ColdDeviceStorage } from "@/store";
|
import { ColdDeviceStorage } from "@/store";
|
||||||
import "vue-plyr/dist/vue-plyr.css";
|
import "vue-plyr/dist/vue-plyr.css";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
media: misskey.entities.DriveFile;
|
media: packed.PackDriveFileBase | misskey.entities.DriveFile;
|
||||||
}>(),
|
}>(),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,12 +53,19 @@
|
||||||
@click="$refs.modal.close()"
|
@click="$refs.modal.close()"
|
||||||
/>
|
/>
|
||||||
<footer>
|
<footer>
|
||||||
<span>{{ image.type }}</span>
|
<span>{{ image.mime_type || image.type }}</span>
|
||||||
<span>{{ bytes(image.size) }}</span>
|
<span>{{ bytes(image.size) }}</span>
|
||||||
<span v-if="image.properties && image.properties.width"
|
<span v-if="image.properties && image.properties.width"
|
||||||
>{{ number(image.properties.width) }}px ×
|
>{{ number(image.properties.width) }}px ×
|
||||||
{{ number(image.properties.height) }}px</span
|
{{ number(image.properties.height) }}px</span
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
image.media_metadata && image.media_metadata.width
|
||||||
|
"
|
||||||
|
>{{ number(image.media_metadata.width) }}px ×
|
||||||
|
{{ number(image.media_metadata.height) }}px</span
|
||||||
|
>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<button v-if="hide" class="qjewsnkg" @click="hide = false">
|
<button v-if="hide" class="qjewsnkg" @click="hide = false">
|
||||||
<ImgWithBlurhash
|
<ImgWithBlurhash
|
||||||
:hash="image.blurhash"
|
:hash="image.blurhash ?? undefined"
|
||||||
:title="image.comment"
|
:title="image.comment"
|
||||||
:alt="image.comment"
|
:alt="image.comment ?? ''"
|
||||||
/>
|
/>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
@ -18,14 +18,21 @@
|
||||||
<div v-else class="gqnyydlz">
|
<div v-else class="gqnyydlz">
|
||||||
<a :href="image.url" :title="image.name">
|
<a :href="image.url" :title="image.name">
|
||||||
<ImgWithBlurhash
|
<ImgWithBlurhash
|
||||||
:hash="image.blurhash"
|
:hash="image.blurhash ?? undefined"
|
||||||
:src="url"
|
:src="url"
|
||||||
:alt="image.comment"
|
:alt="image.comment ?? ''"
|
||||||
:type="image.type"
|
:type="magTransProperty(image, 'mime_type', 'type')"
|
||||||
:title="image.comment"
|
:title="image.comment"
|
||||||
:cover="false"
|
:cover="false"
|
||||||
/>
|
/>
|
||||||
<div v-if="image.type === 'image/gif'" class="gif">GIF</div>
|
<div
|
||||||
|
v-if="
|
||||||
|
magTransProperty(image, 'mime_type', 'type') === 'image/gif'
|
||||||
|
"
|
||||||
|
class="gif"
|
||||||
|
>
|
||||||
|
GIF
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="_image_controls">
|
<div class="_image_controls">
|
||||||
<button
|
<button
|
||||||
|
@ -63,9 +70,11 @@ import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
||||||
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
image: misskey.entities.DriveFile;
|
image: packed.PackDriveFileBase | misskey.entities.DriveFile;
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -95,8 +104,10 @@ const url =
|
||||||
props.raw || defaultStore.state.loadRawImages
|
props.raw || defaultStore.state.loadRawImages
|
||||||
? props.image.url
|
? props.image.url
|
||||||
: defaultStore.state.disableShowingAnimatedImages
|
: defaultStore.state.disableShowingAnimatedImages
|
||||||
? getStaticImageUrl(props.image.thumbnailUrl)
|
? getStaticImageUrl(
|
||||||
: props.image.thumbnailUrl;
|
magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")!
|
||||||
|
)
|
||||||
|
: magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")!;
|
||||||
|
|
||||||
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
||||||
watch(
|
watch(
|
||||||
|
@ -105,7 +116,7 @@ watch(
|
||||||
hide =
|
hide =
|
||||||
defaultStore.state.nsfw === "force"
|
defaultStore.state.nsfw === "force"
|
||||||
? true
|
? true
|
||||||
: props.image.isSensitive &&
|
: magTransProperty(props.image, "sensitive", "isSensitive") &&
|
||||||
defaultStore.state.nsfw !== "ignore";
|
defaultStore.state.nsfw !== "ignore";
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,12 +18,24 @@
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<XVideo
|
<XVideo
|
||||||
v-if="media.type.startsWith('video')"
|
v-if="
|
||||||
|
magTransProperty(
|
||||||
|
media,
|
||||||
|
'mime_type',
|
||||||
|
'type'
|
||||||
|
).startsWith('video')
|
||||||
|
"
|
||||||
:key="media.id"
|
:key="media.id"
|
||||||
:video="media"
|
:video="media"
|
||||||
/>
|
/>
|
||||||
<XImage
|
<XImage
|
||||||
v-else-if="media.type.startsWith('image')"
|
v-else-if="
|
||||||
|
magTransProperty(
|
||||||
|
media,
|
||||||
|
'mime_type',
|
||||||
|
'type'
|
||||||
|
).startsWith('image')
|
||||||
|
"
|
||||||
:key="media.id"
|
:key="media.id"
|
||||||
class="image"
|
class="image"
|
||||||
:data-id="media.id"
|
:data-id="media.id"
|
||||||
|
@ -47,10 +59,11 @@ import XImage from "@/components/MkMediaImage.vue";
|
||||||
import XVideo from "@/components/MkMediaVideo.vue";
|
import XVideo from "@/components/MkMediaVideo.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { FILE_TYPE_BROWSERSAFE } from "@/const";
|
import { FILE_TYPE_BROWSERSAFE } from "@/const";
|
||||||
import { defaultStore } from "@/store";
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mediaList: misskey.entities.DriveFile[];
|
mediaList: (packed.PackDriveFileBase | misskey.entities.DriveFile)[];
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
inDm?: boolean;
|
inDm?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
@ -62,22 +75,30 @@ onMounted(() => {
|
||||||
const lightbox = new PhotoSwipeLightbox({
|
const lightbox = new PhotoSwipeLightbox({
|
||||||
dataSource: props.mediaList
|
dataSource: props.mediaList
|
||||||
.filter((media) => {
|
.filter((media) => {
|
||||||
if (media.type === "image/svg+xml") return true; // svgのwebpublicはpngなのでtrue
|
const type = magTransProperty(media, "mime_type", "type");
|
||||||
|
|
||||||
|
if (type === "image/svg+xml") return true; // svgのwebpublicはpngなのでtrue
|
||||||
return (
|
return (
|
||||||
media.type.startsWith("image") &&
|
type.startsWith("image") &&
|
||||||
FILE_TYPE_BROWSERSAFE.includes(media.type)
|
FILE_TYPE_BROWSERSAFE.includes(type)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((media) => {
|
.map((media) => {
|
||||||
|
const properties = magTransProperty(
|
||||||
|
media,
|
||||||
|
"media_metadata",
|
||||||
|
"properties"
|
||||||
|
);
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
src: media.url,
|
src: media.url,
|
||||||
w: media.properties.width,
|
w: properties.width,
|
||||||
h: media.properties.height,
|
h: properties.height,
|
||||||
alt: media.comment,
|
alt: media.comment,
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
media.properties.orientation != null &&
|
properties.orientation != null &&
|
||||||
media.properties.orientation >= 5
|
properties.orientation >= 5
|
||||||
) {
|
) {
|
||||||
[item.w, item.h] = [item.h, item.w];
|
[item.w, item.h] = [item.h, item.w];
|
||||||
}
|
}
|
||||||
|
@ -116,16 +137,21 @@ onMounted(() => {
|
||||||
const id = element.dataset.id;
|
const id = element.dataset.id;
|
||||||
const file = props.mediaList.find((media) => media.id === id);
|
const file = props.mediaList.find((media) => media.id === id);
|
||||||
|
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const properties = magTransProperty(
|
||||||
|
file,
|
||||||
|
"media_metadata",
|
||||||
|
"properties"
|
||||||
|
);
|
||||||
|
|
||||||
itemData.src = file.url;
|
itemData.src = file.url;
|
||||||
itemData.w = Number(file.properties.width);
|
itemData.w = Number(properties.width);
|
||||||
itemData.h = Number(file.properties.height);
|
itemData.h = Number(properties.height);
|
||||||
if (
|
if (properties.orientation != null && properties.orientation >= 5) {
|
||||||
file.properties.orientation != null &&
|
|
||||||
file.properties.orientation >= 5
|
|
||||||
) {
|
|
||||||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||||
}
|
}
|
||||||
itemData.msrc = file.thumbnailUrl;
|
itemData.msrc = magTransProperty(file, "thumbnail_url", "thumbnailUrl");
|
||||||
itemData.alt = file.comment;
|
itemData.alt = file.comment;
|
||||||
itemData.thumbCropped = true;
|
itemData.thumbCropped = true;
|
||||||
});
|
});
|
||||||
|
@ -178,12 +204,16 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const previewable = (file: misskey.entities.DriveFile): boolean => {
|
const previewable = (
|
||||||
if (file.type === "image/svg+xml") return true; // svgのwebpublic/thumbnailはpngなのでtrue
|
file: packed.PackDriveFileBase | misskey.entities.DriveFile
|
||||||
|
): boolean => {
|
||||||
|
const type = magTransProperty(file, "mime_type", "type");
|
||||||
|
|
||||||
|
if (type === "image/svg+xml") return true; // svgのwebpublic/thumbnailはpngなのでtrue
|
||||||
// FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切
|
// FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切
|
||||||
return (
|
return (
|
||||||
(file.type.startsWith("video") || file.type.startsWith("image")) &&
|
(type.startsWith("video") || type.startsWith("image")) &&
|
||||||
FILE_TYPE_BROWSERSAFE.includes(file.type)
|
FILE_TYPE_BROWSERSAFE.includes(type)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const previewableCount = props.mediaList.filter((media) =>
|
const previewableCount = props.mediaList.filter((media) =>
|
||||||
|
|
|
@ -31,14 +31,19 @@
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
:poster="video.thumbnailUrl"
|
:poster="
|
||||||
|
magTransProperty(video, 'thumbnail_url', 'thumbnailUrl')
|
||||||
|
"
|
||||||
:title="video.comment"
|
:title="video.comment"
|
||||||
:aria-label="video.comment"
|
:aria-label="video.comment"
|
||||||
preload="none"
|
preload="none"
|
||||||
controls
|
controls
|
||||||
@contextmenu.stop
|
@contextmenu.stop
|
||||||
>
|
>
|
||||||
<source :src="video.url" :type="video.type" />
|
<source
|
||||||
|
:src="video.url"
|
||||||
|
:type="magTransProperty(video, 'mime_type', 'type')"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</VuePlyr>
|
</VuePlyr>
|
||||||
<button
|
<button
|
||||||
|
@ -58,9 +63,11 @@ import type * as misskey from "calckey-js";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import "vue-plyr/dist/vue-plyr.css";
|
import "vue-plyr/dist/vue-plyr.css";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
video: misskey.entities.DriveFile;
|
video: packed.PackDriveFileBase | misskey.entities.DriveFile;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const plyr = ref();
|
const plyr = ref();
|
||||||
|
@ -69,7 +76,8 @@ const mini = ref(false);
|
||||||
const hide = ref(
|
const hide = ref(
|
||||||
defaultStore.state.nsfw === "force"
|
defaultStore.state.nsfw === "force"
|
||||||
? true
|
? true
|
||||||
: props.video.isSensitive && defaultStore.state.nsfw !== "ignore"
|
: magTransProperty(props.video, "sensitive", "isSensitive") &&
|
||||||
|
defaultStore.state.nsfw !== "ignore"
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -235,28 +235,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
|
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
||||||
|
import * as mfm from "mfm-js";
|
||||||
import type * as misskey from "calckey-js";
|
import type * as misskey from "calckey-js";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
|
||||||
import XMediaList from "@/components/MkMediaList.vue";
|
|
||||||
import XCwButton from "@/components/MkCwButton.vue";
|
|
||||||
import XPoll from "@/components/MkPoll.vue";
|
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import { focusPrev, focusNext } from "@/scripts/focus";
|
import { focusNext, focusPrev } from "@/scripts/focus";
|
||||||
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
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";
|
||||||
|
@ -268,7 +263,6 @@ import { i18n } from "@/i18n";
|
||||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { notePage } from "@/filters/note";
|
import { notePage } from "@/filters/note";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
import { getNoteSummary } from "@/scripts/get-note-summary";
|
import { getNoteSummary } from "@/scripts/get-note-summary";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -280,7 +274,7 @@ const props = defineProps<{
|
||||||
collapsedReply?: boolean;
|
collapsedReply?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = $ref<misskey.entities.Note>(structuredClone(toRaw(props.note)));
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -295,7 +289,7 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
// plugin
|
// plugin
|
||||||
if (noteViewInterruptors.length > 0) {
|
if (noteViewInterruptors.length > 0) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let result = deepClone(note);
|
let result = structuredClone(toRaw(note));
|
||||||
for (const interruptor of noteViewInterruptors) {
|
for (const interruptor of noteViewInterruptors) {
|
||||||
result = await interruptor.handler(result);
|
result = await interruptor.handler(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,13 @@
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<MkUserName :user="note.user" class="mkusername">
|
<MkUserName :user="note.user" class="mkusername">
|
||||||
<span v-if="note.user.isBot" class="is-bot">bot</span>
|
<span
|
||||||
|
v-if="
|
||||||
|
magTransProperty(note.user, 'is_bot', 'isBot')
|
||||||
|
"
|
||||||
|
class="is-bot"
|
||||||
|
>bot</span
|
||||||
|
>
|
||||||
</MkUserName>
|
</MkUserName>
|
||||||
</MkA>
|
</MkA>
|
||||||
<div class="username"><MkAcct :user="note.user" /></div>
|
<div class="username"><MkAcct :user="note.user" /></div>
|
||||||
|
@ -17,16 +23,38 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<MkA class="created-at" :to="notePage(note)">
|
<MkA class="created-at" :to="notePage(note)">
|
||||||
<MkTime :time="note.createdAt" />
|
<MkTime
|
||||||
|
:time="
|
||||||
|
magTransProperty(
|
||||||
|
note,
|
||||||
|
'created_at',
|
||||||
|
'createdAt'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
<i
|
<i
|
||||||
v-if="note.updatedAt"
|
v-if="
|
||||||
|
magTransProperty(
|
||||||
|
note,
|
||||||
|
'updated_at',
|
||||||
|
'updatedAt'
|
||||||
|
)
|
||||||
|
"
|
||||||
v-tooltip.noDelay="
|
v-tooltip.noDelay="
|
||||||
i18n.t('edited', {
|
i18n.t('edited', {
|
||||||
date: new Date(
|
date: new Date(
|
||||||
note.updatedAt
|
magTransProperty(
|
||||||
|
note,
|
||||||
|
'updated_at',
|
||||||
|
'updatedAt'
|
||||||
|
)!
|
||||||
).toLocaleDateString(),
|
).toLocaleDateString(),
|
||||||
time: new Date(
|
time: new Date(
|
||||||
note.updatedAt
|
magTransProperty(
|
||||||
|
note,
|
||||||
|
'updated_at',
|
||||||
|
'updatedAt'
|
||||||
|
)!
|
||||||
).toLocaleTimeString(),
|
).toLocaleTimeString(),
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
@ -37,7 +65,7 @@
|
||||||
<MkVisibility :note="note" />
|
<MkVisibility :note="note" />
|
||||||
</div>
|
</div>
|
||||||
<MkInstanceTicker
|
<MkInstanceTicker
|
||||||
v-if="showTicker"
|
v-if="showTicker && note.user.instance"
|
||||||
class="ticker"
|
class="ticker"
|
||||||
:instance="note.user.instance"
|
:instance="note.user.instance"
|
||||||
/>
|
/>
|
||||||
|
@ -47,17 +75,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import type * as misskey from "calckey-js";
|
import type * as misskey from "calckey-js";
|
||||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
||||||
import { notePage } from "@/filters/note";
|
import { notePage } from "@/filters/note";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteBase | misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -65,7 +94,7 @@ let note = $ref(props.note);
|
||||||
|
|
||||||
const showTicker =
|
const showTicker =
|
||||||
defaultStore.state.instanceTicker === "always" ||
|
defaultStore.state.instanceTicker === "always" ||
|
||||||
(defaultStore.state.instanceTicker === "remote" && note.user.instance);
|
(defaultStore.state.instanceTicker === "remote" && note.user.host);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -68,32 +68,52 @@
|
||||||
@click="reply()"
|
@click="reply()"
|
||||||
>
|
>
|
||||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||||
<template v-if="appearNote.repliesCount > 0">
|
<template
|
||||||
<p class="count">{{ appearNote.repliesCount }}</p>
|
v-if="
|
||||||
|
Number(
|
||||||
|
magTransProperty(
|
||||||
|
appearNote,
|
||||||
|
'reply_count',
|
||||||
|
'repliesCount'
|
||||||
|
)
|
||||||
|
) > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p class="count">
|
||||||
|
{{
|
||||||
|
magTransProperty(
|
||||||
|
appearNote,
|
||||||
|
"reply_count",
|
||||||
|
"repliesCount"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
<XRenoteButton
|
<XRenoteButton
|
||||||
ref="renoteButton"
|
ref="renoteButton"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="appearNote.renoteCount"
|
:count="
|
||||||
|
Number(
|
||||||
|
magTransProperty(
|
||||||
|
appearNote,
|
||||||
|
'renote_count',
|
||||||
|
'renoteCount'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<XStarButtonNoEmoji
|
<XStarButtonNoEmoji
|
||||||
v-if="!enableEmojiReactions"
|
v-if="!enableEmojiReactions"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="
|
:count="magReactionCount(appearNote)"
|
||||||
Object.values(appearNote.reactions).reduce(
|
:reacted="magHasReacted(appearNote)"
|
||||||
(partialSum, val) => partialSum + val,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:reacted="appearNote.myReaction != null"
|
|
||||||
/>
|
/>
|
||||||
<XStarButton
|
<XStarButton
|
||||||
v-if="
|
v-if="
|
||||||
enableEmojiReactions &&
|
enableEmojiReactions && !magHasReacted(appearNote)
|
||||||
appearNote.myReaction == null
|
|
||||||
"
|
"
|
||||||
ref="starButton"
|
ref="starButton"
|
||||||
class="button"
|
class="button"
|
||||||
|
@ -101,8 +121,7 @@
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="
|
v-if="
|
||||||
enableEmojiReactions &&
|
enableEmojiReactions && !magHasReacted(appearNote)
|
||||||
appearNote.myReaction == null
|
|
||||||
"
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||||
|
@ -112,10 +131,7 @@
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="
|
v-if="enableEmojiReactions && magHasReacted(appearNote)"
|
||||||
enableEmojiReactions &&
|
|
||||||
appearNote.myReaction != null
|
|
||||||
"
|
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click="undoReact(appearNote)"
|
@click="undoReact(appearNote)"
|
||||||
|
@ -162,7 +178,9 @@
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
<template #name>
|
<template #name>
|
||||||
<MkA
|
<MkA
|
||||||
v-user-preview="note.userId"
|
v-user-preview="
|
||||||
|
magTransMap(note, 'user', 'userId', (u) => u.id)
|
||||||
|
"
|
||||||
class="name"
|
class="name"
|
||||||
:to="userPage(note.user)"
|
:to="userPage(note.user)"
|
||||||
>
|
>
|
||||||
|
@ -177,8 +195,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, ref } from "vue";
|
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
|
import { inject, ref, toRaw } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
|
@ -201,13 +219,20 @@ import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magHasReacted,
|
||||||
|
magIsRenote,
|
||||||
|
magReactionCount,
|
||||||
|
magTransMap,
|
||||||
|
magTransProperty,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
conversation?: misskey.entities.Note[];
|
conversation?: misskey.entities.Note[];
|
||||||
parentId?;
|
parentId?;
|
||||||
detailedView?;
|
detailedView?;
|
||||||
|
@ -223,7 +248,9 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = $ref<packed.PackNoteMaybeFull | misskey.entities.Note>(
|
||||||
|
structuredClone(toRaw(props.note))
|
||||||
|
);
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -235,12 +262,6 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
return i18n.ts.userSaysSomething;
|
return i18n.ts.userSaysSomething;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRenote =
|
|
||||||
note.renote != null &&
|
|
||||||
note.text == null &&
|
|
||||||
note.fileIds.length === 0 &&
|
|
||||||
note.poll == null;
|
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement>();
|
||||||
const footerEl = ref<HTMLElement>();
|
const footerEl = ref<HTMLElement>();
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement>();
|
||||||
|
@ -248,7 +269,11 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement>();
|
||||||
let appearNote = $computed(() =>
|
let appearNote = $computed(() =>
|
||||||
isRenote ? (note.renote as misskey.entities.Note) : note
|
magIsRenote(note)
|
||||||
|
? (magTransProperty(note, "renoted_note", "renote") as
|
||||||
|
| packed.PackNoteMaybeFull
|
||||||
|
| misskey.entities.Note)
|
||||||
|
: note
|
||||||
);
|
);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
||||||
|
@ -329,6 +354,8 @@ function menu(viaKeyboard = false): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent): void {
|
function onContextmenu(ev: MouseEvent): void {
|
||||||
|
if (!ev.target || !(ev.target instanceof HTMLElement)) return;
|
||||||
|
|
||||||
const isLink = (el: HTMLElement) => {
|
const isLink = (el: HTMLElement) => {
|
||||||
if (el.tagName === "A") return true;
|
if (el.tagName === "A") return true;
|
||||||
if (el.parentElement) {
|
if (el.parentElement) {
|
||||||
|
@ -336,7 +363,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLink(ev.target)) return;
|
if (isLink(ev.target)) return;
|
||||||
if (window.getSelection().toString() !== "") return;
|
if (window.getSelection()?.toString() !== "") return;
|
||||||
|
|
||||||
if (defaultStore.state.useReactionPickerForContextMenu) {
|
if (defaultStore.state.useReactionPickerForContextMenu) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -395,15 +422,15 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
el.value.focus();
|
el.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function blur() {
|
function blur() {
|
||||||
el.value.blur();
|
el.value?.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteClick(e) {
|
function noteClick(e) {
|
||||||
if (document.getSelection().type === "Range" || !expandOnNoteClick) {
|
if (document.getSelection()?.type === "Range" || !expandOnNoteClick) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
} else {
|
} else {
|
||||||
router.push(notePage(props.note));
|
router.push(notePage(props.note));
|
||||||
|
|
|
@ -2,31 +2,50 @@
|
||||||
<div class="tivcixzd" :class="{ done: closed || isVoted }">
|
<div class="tivcixzd" :class="{ done: closed || isVoted }">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="(choice, i) in note.poll.choices"
|
v-for="(choice, i) in magTransProperty(
|
||||||
|
note.poll,
|
||||||
|
'options',
|
||||||
|
'choices'
|
||||||
|
)"
|
||||||
:key="i"
|
:key="i"
|
||||||
:class="{ voted: choice.voted }"
|
:class="{ voted: magTransProperty(choice, 'voted', 'isVoted') }"
|
||||||
@click.stop="vote(i)"
|
@click.stop="vote(i)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="backdrop"
|
class="backdrop"
|
||||||
:style="{
|
:style="{
|
||||||
width: `${
|
width: `${
|
||||||
showResult ? (choice.votes / total) * 100 : 0
|
showResult
|
||||||
|
? (magTransProperty(
|
||||||
|
choice,
|
||||||
|
'votes_count',
|
||||||
|
'votes'
|
||||||
|
) /
|
||||||
|
total) *
|
||||||
|
100
|
||||||
|
: 0
|
||||||
}%`,
|
}%`,
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<span>
|
<span>
|
||||||
<template v-if="choice.isVoted"
|
<template
|
||||||
|
v-if="magTransProperty(choice, 'voted', 'isVoted')"
|
||||||
><i class="ph-check ph-bold ph-lg"></i
|
><i class="ph-check ph-bold ph-lg"></i
|
||||||
></template>
|
></template>
|
||||||
<Mfm
|
<Mfm
|
||||||
:text="choice.text"
|
:text="magTransProperty(choice, 'title', 'text')"
|
||||||
:plain="true"
|
:plain="true"
|
||||||
:custom-emojis="note.emojis"
|
:custom-emojis="note.emojis"
|
||||||
/>
|
/>
|
||||||
<span v-if="showResult" class="votes"
|
<span v-if="showResult" class="votes"
|
||||||
>({{
|
>({{
|
||||||
i18n.t("_poll.votesCount", { n: choice.votes })
|
i18n.t("_poll.votesCount", {
|
||||||
|
n: magTransProperty(
|
||||||
|
choice,
|
||||||
|
"votes_count",
|
||||||
|
"votes"
|
||||||
|
),
|
||||||
|
})
|
||||||
}})</span
|
}})</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
@ -65,22 +84,35 @@ import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { useInterval } from "@/scripts/use-interval";
|
import { useInterval } from "@/scripts/use-interval";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note & Required<Pick<misskey.entities.Note, "poll">>;
|
note:
|
||||||
|
| (packed.PackNoteMaybeFull & { poll: {} })
|
||||||
|
| (misskey.entities.Note &
|
||||||
|
Required<Pick<misskey.entities.Note, "poll">>);
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pollRefreshing = ref(false);
|
const pollRefreshing = ref(false);
|
||||||
const remaining = ref(-1);
|
const remaining = ref(-1);
|
||||||
|
|
||||||
const total = computed(() => sum(props.note.poll.choices.map((x) => x.votes)));
|
const total = computed(() =>
|
||||||
|
sum(
|
||||||
|
magTransProperty(props.note.poll, "options", "choices").map(
|
||||||
|
(x) => magTransProperty(x, "votes_count", "votes") as number
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
const closed = computed(() => remaining.value === 0);
|
const closed = computed(() => remaining.value === 0);
|
||||||
const isLocal = computed(() => !props.note.uri);
|
const isLocal = computed(() => !props.note.uri);
|
||||||
const isVoted = computed(
|
const isVoted = computed(
|
||||||
() =>
|
() =>
|
||||||
!props.note.poll.multiple &&
|
!magTransProperty(props.note.poll, "multiple_choice", "multiple") &&
|
||||||
props.note.poll.choices.some((c) => c.isVoted)
|
magTransProperty(props.note.poll, "options", "choices").some(
|
||||||
|
(c) => magTransProperty(c, "voted", "isVoted") ?? false
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const timer = computed(() =>
|
const timer = computed(() =>
|
||||||
i18n.t(
|
i18n.t(
|
||||||
|
@ -103,11 +135,17 @@ const timer = computed(() =>
|
||||||
const showResult = ref(props.readOnly || isVoted.value);
|
const showResult = ref(props.readOnly || isVoted.value);
|
||||||
|
|
||||||
// 期限付きアンケート
|
// 期限付きアンケート
|
||||||
if (props.note.poll.expiresAt) {
|
if (magTransProperty(props.note.poll, "expires_at", "expiresAt")) {
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
remaining.value = Math.floor(
|
remaining.value = Math.floor(
|
||||||
Math.max(
|
Math.max(
|
||||||
new Date(props.note.poll.expiresAt!).getTime() - Date.now(),
|
new Date(
|
||||||
|
magTransProperty(
|
||||||
|
props.note.poll,
|
||||||
|
"expires_at",
|
||||||
|
"expiresAt"
|
||||||
|
)!
|
||||||
|
).getTime() - Date.now(),
|
||||||
0
|
0
|
||||||
) / 1000
|
) / 1000
|
||||||
);
|
);
|
||||||
|
@ -152,7 +190,11 @@ const vote = async (id: number) => {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "question",
|
type: "question",
|
||||||
text: i18n.t("voteConfirm", {
|
text: i18n.t("voteConfirm", {
|
||||||
choice: props.note.poll.choices[id].text,
|
choice: magTransProperty(
|
||||||
|
magTransProperty(props.note.poll, "options", "choices")[id],
|
||||||
|
"title",
|
||||||
|
"text"
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
@ -161,7 +203,12 @@ const vote = async (id: number) => {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
choice: id,
|
choice: id,
|
||||||
});
|
});
|
||||||
if (!showResult.value) showResult.value = !props.note.poll.multiple;
|
if (!showResult.value)
|
||||||
|
showResult.value = !magTransProperty(
|
||||||
|
props.note.poll,
|
||||||
|
"multiple_choice",
|
||||||
|
"multiple"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -252,24 +252,32 @@ import {
|
||||||
openAccountMenu as openAccountMenu_,
|
openAccountMenu as openAccountMenu_,
|
||||||
} from "@/account";
|
} from "@/account";
|
||||||
import { uploadFile } from "@/scripts/upload";
|
import { uploadFile } from "@/scripts/upload";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
||||||
import { preprocess } from "@/scripts/preprocess";
|
import { preprocess } from "@/scripts/preprocess";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magLegacyVisibility,
|
||||||
|
magTransMap,
|
||||||
|
magTransProperty,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const modal = inject("modal");
|
const modal = inject("modal");
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
reply?: misskey.entities.Note;
|
reply?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
renote?: misskey.entities.Note;
|
renote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
mention?: misskey.entities.User;
|
mention?: misskey.entities.User;
|
||||||
specified?: misskey.entities.User;
|
specified?: misskey.entities.User;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
initialVisibility?: typeof misskey.noteVisibilities;
|
initialVisibility?: typeof misskey.noteVisibilities;
|
||||||
initialFiles?: misskey.entities.DriveFile[];
|
initialFiles?: (
|
||||||
|
| packed.PackDriveFileBase
|
||||||
|
| misskey.entities.DriveFile
|
||||||
|
)[];
|
||||||
initialLocalOnly?: boolean;
|
initialLocalOnly?: boolean;
|
||||||
initialVisibleUsers?: misskey.entities.User[];
|
initialVisibleUsers?: misskey.entities.User[];
|
||||||
initialNote?: misskey.entities.Note;
|
initialNote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
|
@ -465,21 +473,26 @@ if (
|
||||||
) {
|
) {
|
||||||
visibility = "specified";
|
visibility = "specified";
|
||||||
} else {
|
} else {
|
||||||
visibility = props.reply.visibility;
|
visibility = magLegacyVisibility(props.reply.visibility);
|
||||||
}
|
}
|
||||||
if (visibility === "specified") {
|
if (visibility === "specified") {
|
||||||
if (props.reply.visibleUserIds) {
|
const ids = magTransProperty(
|
||||||
|
props.reply,
|
||||||
|
"visible_user_ids",
|
||||||
|
"visibleUserIds"
|
||||||
|
);
|
||||||
|
if (ids) {
|
||||||
os.api("users/show", {
|
os.api("users/show", {
|
||||||
userIds: props.reply.visibleUserIds.filter(
|
userIds: ids.filter(
|
||||||
(uid) => uid !== $i.id && uid !== props.reply.userId
|
(uid) => uid !== $i.id && uid !== props.reply!.user.id
|
||||||
),
|
),
|
||||||
}).then((users) => {
|
}).then((users) => {
|
||||||
users.forEach(pushVisibleUser);
|
users.forEach(pushVisibleUser);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.reply.userId !== $i.id) {
|
if (props.reply.user.id !== $i.id) {
|
||||||
os.api("users/show", { userId: props.reply.userId }).then(
|
os.api("users/show", { userId: props.reply.user.id }).then(
|
||||||
(user) => {
|
(user) => {
|
||||||
pushVisibleUser(user);
|
pushVisibleUser(user);
|
||||||
}
|
}
|
||||||
|
@ -591,7 +604,10 @@ function updateFiles(_files) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFileSensitive(file, sensitive) {
|
function updateFileSensitive(file, sensitive) {
|
||||||
files[files.findIndex((x) => x.id === file.id)].isSensitive = sensitive;
|
const idx = files.findIndex((x) => x.id === file.id);
|
||||||
|
const f = files[idx];
|
||||||
|
if ("isSensitive" in f) f.isSensitive = sensitive;
|
||||||
|
else f.sensitive = sensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFileName(file, name) {
|
function updateFileName(file, name) {
|
||||||
|
@ -830,7 +846,7 @@ async function post() {
|
||||||
// plugin
|
// plugin
|
||||||
if (notePostInterruptors.length > 0) {
|
if (notePostInterruptors.length > 0) {
|
||||||
for (const interruptor of notePostInterruptors) {
|
for (const interruptor of notePostInterruptors) {
|
||||||
postData = await interruptor.handler(deepClone(postData));
|
postData = await interruptor.handler(structuredClone(postData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -972,20 +988,39 @@ onMounted(() => {
|
||||||
if (props.initialNote) {
|
if (props.initialNote) {
|
||||||
const init = props.initialNote;
|
const init = props.initialNote;
|
||||||
text = init.text ? init.text : "";
|
text = init.text ? init.text : "";
|
||||||
files = init.files;
|
|
||||||
|
files = magTransProperty(init, "attachments", "files") ?? [];
|
||||||
cw = init.cw;
|
cw = init.cw;
|
||||||
useCw = init.cw != null;
|
useCw = init.cw != null;
|
||||||
if (init.poll) {
|
if (init.poll) {
|
||||||
poll = {
|
poll = {
|
||||||
choices: init.poll.choices.map((x) => x.text),
|
choices: magTransMap(
|
||||||
multiple: init.poll.multiple,
|
init.poll,
|
||||||
expiresAt: init.poll.expiresAt,
|
"options",
|
||||||
expiredAfter: init.poll.expiredAfter,
|
"choices",
|
||||||
|
(a) => a.map((x) => x.title),
|
||||||
|
(b) => b.map((x) => x.text)
|
||||||
|
),
|
||||||
|
multiple: magTransProperty(
|
||||||
|
init.poll,
|
||||||
|
"multiple_choice",
|
||||||
|
"multiple"
|
||||||
|
),
|
||||||
|
expiresAt: magTransProperty(
|
||||||
|
init.poll,
|
||||||
|
"expires_at",
|
||||||
|
"expiresAt"
|
||||||
|
),
|
||||||
|
// TODO(Natty)
|
||||||
|
expiredAfter: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
visibility = init.visibility;
|
visibility = magLegacyVisibility(init.visibility);
|
||||||
localOnly = init.localOnly;
|
localOnly =
|
||||||
quoteId = init.renote ? init.renote.id : null;
|
magTransProperty(init, "local_only", "localOnly") ?? false;
|
||||||
|
quoteId = magTransProperty(init, "renote", "renoted_note")
|
||||||
|
? magTransProperty(init, "renote", "renoted_note")!.id
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => watchForDraft());
|
nextTick(() => watchForDraft());
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
:file="element"
|
:file="element"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
/>
|
/>
|
||||||
<div v-if="element.isSensitive" class="sensitive">
|
<div
|
||||||
|
v-if="element.sensitive ?? element.isSensitive"
|
||||||
|
class="sensitive"
|
||||||
|
>
|
||||||
<i class="ph-warning ph-bold ph-lg icon"></i>
|
<i class="ph-warning ph-bold ph-lg icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +34,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from "vue";
|
import { defineAsyncComponent, defineComponent } from "vue";
|
||||||
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -86,9 +89,13 @@ export default defineComponent({
|
||||||
toggleSensitive(file) {
|
toggleSensitive(file) {
|
||||||
os.api("drive/files/update", {
|
os.api("drive/files/update", {
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
isSensitive: !file.isSensitive,
|
isSensitive: !(file.sensitive ?? file.isSensitive),
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.$emit("changeSensitive", file, !file.isSensitive);
|
this.$emit(
|
||||||
|
"changeSensitive",
|
||||||
|
file,
|
||||||
|
!(file.sensitive ?? file.isSensitive)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async rename(file) {
|
async rename(file) {
|
||||||
|
@ -150,9 +157,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: file.isSensitive
|
text:
|
||||||
? i18n.ts.unmarkAsSensitive
|
file.sensitive ?? file.isSensitive
|
||||||
: i18n.ts.markAsSensitive,
|
? i18n.ts.unmarkAsSensitive
|
||||||
|
: i18n.ts.markAsSensitive,
|
||||||
icon: file.isSensitive
|
icon: file.isSensitive
|
||||||
? "ph-eye ph-bold ph-lg"
|
? "ph-eye ph-bold ph-lg"
|
||||||
: "ph-eye-slash ph-bold ph-lg",
|
: "ph-eye-slash ph-bold ph-lg",
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkPostForm from "@/components/MkPostForm.vue";
|
import MkPostForm from "@/components/MkPostForm.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reply?: misskey.entities.Note;
|
reply?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
renote?: misskey.entities.Note;
|
renote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
mention?: misskey.entities.User;
|
mention?: misskey.entities.User;
|
||||||
specified?: misskey.entities.User;
|
specified?: misskey.entities.User;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
|
@ -34,7 +34,7 @@ const props = defineProps<{
|
||||||
initialFiles?: misskey.entities.DriveFile[];
|
initialFiles?: misskey.entities.DriveFile[];
|
||||||
initialLocalOnly?: boolean;
|
initialLocalOnly?: boolean;
|
||||||
initialVisibleUsers?: misskey.entities.User[];
|
initialVisibleUsers?: misskey.entities.User[];
|
||||||
initialNote?: misskey.entities.Note;
|
initialNote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
v-ripple="canToggle"
|
v-ripple="canToggle"
|
||||||
class="hkzvhatu _button"
|
class="hkzvhatu _button"
|
||||||
:class="{
|
:class="{
|
||||||
reacted: note.myReaction == reaction,
|
reacted: magReactionSelf(note) === reaction,
|
||||||
canToggle,
|
canToggle,
|
||||||
newlyAdded: !isInitial,
|
newlyAdded: !isInitial,
|
||||||
}"
|
}"
|
||||||
|
@ -28,12 +28,14 @@ import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magReactionSelf } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reaction: string;
|
reaction: string;
|
||||||
count: number;
|
count: number;
|
||||||
isInitial: boolean;
|
isInitial: boolean;
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const buttonRef = ref<HTMLElement>();
|
const buttonRef = ref<HTMLElement>();
|
||||||
|
@ -43,7 +45,7 @@ const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
|
||||||
const toggleReaction = () => {
|
const toggleReaction = () => {
|
||||||
if (!canToggle.value) return;
|
if (!canToggle.value) return;
|
||||||
|
|
||||||
const oldReaction = props.note.myReaction;
|
const oldReaction = magReactionSelf(props.note);
|
||||||
if (oldReaction) {
|
if (oldReaction) {
|
||||||
os.api("notes/reactions/delete", {
|
os.api("notes/reactions/delete", {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tdflqwzn" :class="{ isMe }">
|
<div class="tdflqwzn" :class="{ isMe }">
|
||||||
<XReaction
|
<XReaction
|
||||||
v-for="(count, reaction) in note.reactions"
|
v-for="r in Array.isArray(note.reactions)
|
||||||
:key="reaction"
|
? note.reactions
|
||||||
:reaction="reaction"
|
: Object.entries(note.reactions)"
|
||||||
:count="count"
|
:key="magReactionPairToLegacy(r)[0]"
|
||||||
:is-initial="initialReactions.has(reaction)"
|
:reaction="magReactionPairToLegacy(r)[0]"
|
||||||
|
:count="magReactionPairToLegacy(r)[1]"
|
||||||
|
:is-initial="initialReactions.has(magReactionPairToLegacy(r)[0])"
|
||||||
:note="note"
|
:note="note"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,14 +18,16 @@ import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import XReaction from "@/components/MkReactionsViewer.reaction.vue";
|
import XReaction from "@/components/MkReactionsViewer.reaction.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magReactionPairToLegacy } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const initialReactions = new Set(Object.keys(props.note.reactions));
|
const initialReactions = new Set(Object.keys(props.note.reactions));
|
||||||
|
|
||||||
const isMe = computed(() => $i && $i.id === props.note.userId);
|
const isMe = computed(() => $i && $i.id === props.note.user.id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import Ripple from "@/components/MkRipple.vue";
|
import Ripple from "@/components/MkRipple.vue";
|
||||||
import XDetails from "@/components/MkUsersTooltip.vue";
|
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -33,21 +32,37 @@ import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { MenuItem } from "@/types/menu";
|
import { MenuItem } from "@/types/menu";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magLegacyVisibility,
|
||||||
|
magTransMap,
|
||||||
|
magTransProperty,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note;
|
||||||
count: number;
|
count: number;
|
||||||
detailedView?;
|
detailedView?;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const buttonRef = ref<HTMLElement>();
|
const buttonRef = ref<HTMLElement>();
|
||||||
|
|
||||||
const hasRenotedBefore = ref(props.note.hasRenotedBefore ?? false);
|
const hasRenotedBefore = ref<boolean>(
|
||||||
|
magTransMap(
|
||||||
|
props.note,
|
||||||
|
"self_renote_count",
|
||||||
|
"hasRenotedBefore",
|
||||||
|
(n) => (n ?? 0) > 0
|
||||||
|
) ?? false
|
||||||
|
);
|
||||||
|
|
||||||
const canRenote = computed(
|
const canRenote = computed(
|
||||||
() =>
|
() =>
|
||||||
["public", "home"].includes(props.note.visibility) ||
|
["public", "home"].includes(props.note.visibility) ||
|
||||||
props.note.userId === $i.id
|
($i &&
|
||||||
|
magTransMap(props.note, "user", "userId", (user) => user.id) ===
|
||||||
|
$i.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
useTooltip(buttonRef, async (showing) => {
|
useTooltip(buttonRef, async (showing) => {
|
||||||
|
@ -141,7 +156,12 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
os.api("notes/create", {
|
os.api("notes/create", {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: "specified",
|
visibility: "specified",
|
||||||
visibleUserIds: props.note.visibleUserIds,
|
visibleUserIds:
|
||||||
|
magTransProperty(
|
||||||
|
props.note,
|
||||||
|
"visible_user_ids",
|
||||||
|
"visibleUserIds"
|
||||||
|
) ?? [],
|
||||||
});
|
});
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
|
@ -193,16 +213,25 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
action: () => {
|
action: () => {
|
||||||
os.api(
|
os.api(
|
||||||
"notes/create",
|
"notes/create",
|
||||||
props.note.visibility === "specified"
|
magLegacyVisibility(props.note.visibility) === "specified"
|
||||||
? {
|
? {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: props.note.visibility,
|
visibility: magLegacyVisibility(
|
||||||
visibleUserIds: props.note.visibleUserIds,
|
props.note.visibility
|
||||||
|
),
|
||||||
|
visibleUserIds:
|
||||||
|
magTransProperty(
|
||||||
|
props.note,
|
||||||
|
"visible_user_ids",
|
||||||
|
"visibleUserIds"
|
||||||
|
) ?? [],
|
||||||
localOnly: true,
|
localOnly: true,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: props.note.visibility,
|
visibility: magLegacyVisibility(
|
||||||
|
props.note.visibility
|
||||||
|
),
|
||||||
localOnly: true,
|
localOnly: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -45,9 +45,10 @@ import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: Note;
|
note: packed.PackNoteBase | Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function star(ev?: MouseEvent): void {
|
function star(ev?: MouseEvent): void {
|
||||||
|
|
|
@ -47,13 +47,13 @@ import Ripple from "@/components/MkRipple.vue";
|
||||||
import XDetails from "@/components/MkUsersTooltip.vue";
|
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: Note;
|
note: packed.PackNoteBase | Note;
|
||||||
count: number;
|
count: number;
|
||||||
reacted: boolean;
|
reacted: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<p v-if="note.cw != null" class="cw">
|
<p v-if="note.cw != null" class="cw">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="conversation && note.renoteId == parentId"
|
v-if="
|
||||||
|
conversation &&
|
||||||
|
magTransProperty(note, 'renoted_note_id', 'renoteId') ==
|
||||||
|
parentId
|
||||||
|
"
|
||||||
:to="
|
:to="
|
||||||
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
||||||
"
|
"
|
||||||
|
@ -12,11 +16,17 @@
|
||||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
v-else-if="!detailed && note.replyId"
|
v-else-if="
|
||||||
|
!detailed && magTransProperty(note, 'parent_note_id', 'replyId')
|
||||||
|
"
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${note.replyId}`
|
? `#${magTransProperty(note, 'parent_note_id', 'replyId')}`
|
||||||
: `${notePage(note)}#${note.replyId}`
|
: `${notePage(note)}#${magTransProperty(
|
||||||
|
note,
|
||||||
|
'parent_note_id',
|
||||||
|
'replyId'
|
||||||
|
)}`
|
||||||
"
|
"
|
||||||
behavior="browser"
|
behavior="browser"
|
||||||
v-tooltip="i18n.ts.jumpToPrevious"
|
v-tooltip="i18n.ts.jumpToPrevious"
|
||||||
|
@ -40,7 +50,8 @@
|
||||||
:class="{
|
:class="{
|
||||||
collapsed,
|
collapsed,
|
||||||
isLong,
|
isLong,
|
||||||
manyImages: note.files.length > 4,
|
manyImages:
|
||||||
|
magTransProperty(note, 'attachments', 'files')?.length > 4,
|
||||||
showContent: note.cw && !showContent,
|
showContent: note.cw && !showContent,
|
||||||
animatedMfm: !disableMfm,
|
animatedMfm: !disableMfm,
|
||||||
}"
|
}"
|
||||||
|
@ -66,12 +77,16 @@
|
||||||
tabindex: !showContent ? '-1' : null,
|
tabindex: !showContent ? '-1' : null,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span v-if="note.deletedAt" style="opacity: 0.5"
|
|
||||||
>({{ i18n.ts.deleted }})</span
|
|
||||||
>
|
|
||||||
<template v-if="!note.cw">
|
<template v-if="!note.cw">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="conversation && note.renoteId == parentId"
|
v-if="
|
||||||
|
conversation &&
|
||||||
|
magTransProperty(
|
||||||
|
note,
|
||||||
|
'renoted_note_id',
|
||||||
|
'renoteId'
|
||||||
|
) == parentId
|
||||||
|
"
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${parentId}`
|
? `#${parentId}`
|
||||||
|
@ -84,11 +99,22 @@
|
||||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
v-else-if="!detailed && note.replyId"
|
v-else-if="
|
||||||
|
!detailed &&
|
||||||
|
magTransProperty(note, 'parent_note_id', 'replyId')
|
||||||
|
"
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${note.replyId}`
|
? `#${magTransProperty(
|
||||||
: `${notePage(note)}#${note.replyId}`
|
note,
|
||||||
|
'parent_note_id',
|
||||||
|
'replyId'
|
||||||
|
)}`
|
||||||
|
: `${notePage(note)}#${magTransProperty(
|
||||||
|
note,
|
||||||
|
'parent_note_id',
|
||||||
|
'replyId'
|
||||||
|
)}`
|
||||||
"
|
"
|
||||||
behavior="browser"
|
behavior="browser"
|
||||||
v-tooltip="i18n.ts.jumpToPrevious"
|
v-tooltip="i18n.ts.jumpToPrevious"
|
||||||
|
@ -106,14 +132,24 @@
|
||||||
:custom-emojis="note.emojis"
|
:custom-emojis="note.emojis"
|
||||||
/>
|
/>
|
||||||
<MkA
|
<MkA
|
||||||
v-if="!detailed && note.renoteId"
|
v-if="
|
||||||
|
!detailed &&
|
||||||
|
magTransProperty(note, 'renoted_note_id', 'renoteId')
|
||||||
|
"
|
||||||
class="rp"
|
class="rp"
|
||||||
:to="`/notes/${note.renoteId}`"
|
:to="`/notes/${magTransProperty(
|
||||||
|
note,
|
||||||
|
'renoted_note_id',
|
||||||
|
'renoteId'
|
||||||
|
)}`"
|
||||||
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
||||||
>
|
>
|
||||||
<XMediaList
|
<XMediaList
|
||||||
v-if="note.files.length > 0"
|
v-if="
|
||||||
:media-list="note.files"
|
magTransProperty(note, 'attachments', 'files')?.length >
|
||||||
|
0
|
||||||
|
"
|
||||||
|
:media-list="magTransProperty(note, 'attachments', 'files')!"
|
||||||
/>
|
/>
|
||||||
<XPoll v-if="note.poll" :note="note" class="poll" />
|
<XPoll v-if="note.poll" :note="note" class="poll" />
|
||||||
<template v-if="detailed">
|
<template v-if="detailed">
|
||||||
|
@ -126,11 +162,18 @@
|
||||||
class="url-preview"
|
class="url-preview"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="note.renote"
|
v-if="magTransProperty(note, 'renoted_note', 'renote')"
|
||||||
class="renote"
|
class="renote"
|
||||||
@click.stop="emit('push', note.renote)"
|
@click.stop="
|
||||||
|
emit(
|
||||||
|
'push',
|
||||||
|
magTransProperty(note, 'renoted_note', 'renote')
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<XNoteSimple :note="note.renote" />
|
<XNoteSimple
|
||||||
|
:note="magTransProperty(note, 'renoted_note', 'renote')!"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
|
@ -192,9 +235,12 @@ import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
|
||||||
import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
|
import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
import { $i } from "@/account";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
parentId?;
|
parentId?;
|
||||||
conversation?;
|
conversation?;
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
|
@ -216,7 +262,8 @@ const isLong =
|
||||||
((props.note.text != null &&
|
((props.note.text != null &&
|
||||||
(props.note.text.split("\n").length > 10 ||
|
(props.note.text.split("\n").length > 10 ||
|
||||||
props.note.text.length > 800)) ||
|
props.note.text.length > 800)) ||
|
||||||
props.note.files.length > 4);
|
(magTransProperty(props.note, "attachments", "files") ?? []).length >
|
||||||
|
4);
|
||||||
const collapsed = $ref(props.note.cw == null && isLong);
|
const collapsed = $ref(props.note.cw == null && isLong);
|
||||||
const urls = props.note.text
|
const urls = props.note.text
|
||||||
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-tooltip="text" class="fzgwjkgc" :class="user.onlineStatus"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
user: misskey.entities.User;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const text = $computed(() => {
|
|
||||||
switch (props.user.onlineStatus) {
|
|
||||||
case "online":
|
|
||||||
return i18n.ts.online;
|
|
||||||
case "active":
|
|
||||||
return i18n.ts.active;
|
|
||||||
case "offline":
|
|
||||||
return i18n.ts.offline;
|
|
||||||
case "unknown":
|
|
||||||
return i18n.ts.unknown;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.fzgwjkgc {
|
|
||||||
box-shadow: 0 0 0 3px var(--panel);
|
|
||||||
border-radius: 120%; // Blinkのバグか知らんけど、100%ぴったりにすると何故か若干楕円でレンダリングされる
|
|
||||||
|
|
||||||
&.online {
|
|
||||||
background: #9ccfd8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: #f6c177;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.offline {
|
|
||||||
background: #eb6f92;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unknown {
|
|
||||||
background: #6e6a86;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -13,9 +13,10 @@
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { toUnicode } from "punycode/";
|
import { toUnicode } from "punycode/";
|
||||||
import { host as hostRaw } from "@/config";
|
import { host as hostRaw } from "@/config";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.UserDetailed;
|
user: packed.PackUserBase | misskey.entities.User;
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -3,23 +3,36 @@
|
||||||
v-if="disableLink"
|
v-if="disableLink"
|
||||||
v-user-preview="disablePreview ? undefined : user.id"
|
v-user-preview="disablePreview ? undefined : user.id"
|
||||||
class="eiwwqkts _noSelect"
|
class="eiwwqkts _noSelect"
|
||||||
:class="{ cat: user.isCat, square: $store.state.squareAvatars }"
|
:class="{
|
||||||
|
cat:
|
||||||
|
magTransMap(
|
||||||
|
props.user,
|
||||||
|
'avatar_decoration',
|
||||||
|
'isCat',
|
||||||
|
(a) => a === 'CatEars'
|
||||||
|
) || false,
|
||||||
|
square: $store.state.squareAvatars,
|
||||||
|
}"
|
||||||
:style="{ color }"
|
:style="{ color }"
|
||||||
:title="acct(user)"
|
:title="acct(user)"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<img class="inner" :src="url" decoding="async" />
|
<img class="inner" :src="url" decoding="async" />
|
||||||
<MkUserOnlineIndicator
|
|
||||||
v-if="showIndicator && user.instance == null"
|
|
||||||
class="indicator"
|
|
||||||
:user="user"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<MkA
|
<MkA
|
||||||
v-else
|
v-else
|
||||||
v-user-preview="disablePreview ? undefined : user.id"
|
v-user-preview="disablePreview ? undefined : user.id"
|
||||||
class="eiwwqkts _noSelect"
|
class="eiwwqkts _noSelect"
|
||||||
:class="{ cat: user.isCat, square: $store.state.squareAvatars }"
|
:class="{
|
||||||
|
cat:
|
||||||
|
magTransMap(
|
||||||
|
props.user,
|
||||||
|
'avatar_decoration',
|
||||||
|
'isCat',
|
||||||
|
(d) => d === 'CatEars'
|
||||||
|
) || false,
|
||||||
|
square: $store.state.squareAvatars,
|
||||||
|
}"
|
||||||
:style="{ color }"
|
:style="{ color }"
|
||||||
:to="userPage(user)"
|
:to="userPage(user)"
|
||||||
:title="acct(user)"
|
:title="acct(user)"
|
||||||
|
@ -27,36 +40,30 @@
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<img class="inner" :src="url" decoding="async" />
|
<img class="inner" :src="url" decoding="async" />
|
||||||
<MkUserOnlineIndicator
|
|
||||||
v-if="showIndicator && user.instance == null"
|
|
||||||
class="indicator"
|
|
||||||
:user="user"
|
|
||||||
/>
|
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch } from "vue";
|
import { watch } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
||||||
import { extractAvgColorFromBlurhash } from "@/scripts/extract-avg-color-from-blurhash";
|
import { extractAvgColorFromBlurhash } from "@/scripts/extract-avg-color-from-blurhash";
|
||||||
import { acct, userPage } from "@/filters/user";
|
import { acct, userPage } from "@/filters/user";
|
||||||
import MkUserOnlineIndicator from "@/components/MkUserOnlineIndicator.vue";
|
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransMap, magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase | misskey.entities.User;
|
||||||
target?: string | null;
|
target?: string | null;
|
||||||
disableLink?: boolean;
|
disableLink?: boolean;
|
||||||
disablePreview?: boolean;
|
disablePreview?: boolean;
|
||||||
showIndicator?: boolean;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
target: null,
|
target: null,
|
||||||
disableLink: false,
|
disableLink: false,
|
||||||
disablePreview: false,
|
disablePreview: false,
|
||||||
showIndicator: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -66,8 +73,10 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const url = $computed(() =>
|
const url = $computed(() =>
|
||||||
defaultStore.state.disableShowingAnimatedImages
|
defaultStore.state.disableShowingAnimatedImages
|
||||||
? getStaticImageUrl(props.user.avatarUrl)
|
? getStaticImageUrl(
|
||||||
: props.user.avatarUrl
|
magTransProperty(props.user, "avatar_url", "avatarUrl")
|
||||||
|
)
|
||||||
|
: magTransProperty(props.user, "avatar_url", "avatarUrl")
|
||||||
);
|
);
|
||||||
|
|
||||||
function onClick(ev: MouseEvent) {
|
function onClick(ev: MouseEvent) {
|
||||||
|
@ -77,9 +86,11 @@ function onClick(ev: MouseEvent) {
|
||||||
let color = $ref();
|
let color = $ref();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.user.avatarBlurhash,
|
() => magTransProperty(props.user, "avatar_blurhash", "avatarBlurhash"),
|
||||||
() => {
|
() => {
|
||||||
color = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
|
color = extractAvgColorFromBlurhash(
|
||||||
|
magTransProperty(props.user, "avatar_blurhash", "avatarBlurhash")
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Mfm
|
<Mfm
|
||||||
:class="$style.root"
|
:class="$style.root"
|
||||||
:text="user.name || user.username"
|
:text="magTransUsername(user)"
|
||||||
:plain="true"
|
:plain="true"
|
||||||
:nowrap="nowrap"
|
:nowrap="nowrap"
|
||||||
:custom-emojis="user.emojis"
|
:custom-emojis="user.emojis"
|
||||||
|
@ -9,12 +9,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase | misskey.entities.User;
|
||||||
nowrap?: boolean;
|
nowrap?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, PropType, Ref, ref } from "vue";
|
import { defineComponent, onMounted, PropType, Ref, ref } from "vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNote from "@/components/MagNote.vue";
|
||||||
import XNoteDetailed from "@/components/MkNoteDetailed.vue";
|
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { NoteBlock } from "@/scripts/hpml/block";
|
import { NoteBlock } from "@/scripts/hpml/block";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -32,14 +33,18 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, ctx) {
|
setup(props, ctx) {
|
||||||
const note: Ref<Record<string, any> | null> = ref(null);
|
const note: Ref<packed.PackNoteMaybeFull | null> = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
os.api("notes/show", { noteId: props.block.note }).then(
|
if (!props.block.note) return;
|
||||||
(result) => {
|
|
||||||
note.value = result;
|
os.magApi(
|
||||||
}
|
endpoints.GetNoteById,
|
||||||
);
|
{ context: props.block.detailed, attachments: true },
|
||||||
|
{ id: props.block.note }
|
||||||
|
).then((result) => {
|
||||||
|
note.value = result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -15,6 +15,6 @@ export const lang = localStorage.getItem("lang");
|
||||||
export const langs = _LANGS_;
|
export const langs = _LANGS_;
|
||||||
export const locale = JSON.parse(localStorage.getItem("locale"));
|
export const locale = JSON.parse(localStorage.getItem("locale"));
|
||||||
export const version = _VERSION_;
|
export const version = _VERSION_;
|
||||||
export const instanceName = siteName === "Magnetar" ? host : siteName;
|
export const instanceName = siteName === "Magnetar" ? "" + host : siteName;
|
||||||
export const ui = localStorage.getItem("ui");
|
export const ui = localStorage.getItem("ui");
|
||||||
export const debug = localStorage.getItem("debug") === "true";
|
export const debug = localStorage.getItem("debug") === "true";
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
export const notePage = (note) => {
|
import * as Misskey from "calckey-js";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
|
export const notePage = (note: {
|
||||||
|
id: packed.PackNoteBase["id"] | Misskey.entities.Note["id"];
|
||||||
|
}) => {
|
||||||
return `/notes/${note.id}`;
|
return `/notes/${note.id}`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,36 +11,28 @@ import "@phosphor-icons/web/fill";
|
||||||
|
|
||||||
//#region account indexedDB migration
|
//#region account indexedDB migration
|
||||||
import { set } from "@/scripts/idb-proxy";
|
import { set } from "@/scripts/idb-proxy";
|
||||||
|
|
||||||
const accounts = localStorage.getItem("accounts");
|
|
||||||
if (accounts) {
|
|
||||||
set("accounts", JSON.parse(accounts));
|
|
||||||
localStorage.removeItem("accounts");
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
createApp,
|
createApp,
|
||||||
watch,
|
defineAsyncComponent,
|
||||||
markRaw,
|
markRaw,
|
||||||
version as vueVersion,
|
version as vueVersion,
|
||||||
defineAsyncComponent,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { compareVersions } from "compare-versions";
|
import { compareVersions } from "compare-versions";
|
||||||
|
|
||||||
import widgets from "@/widgets";
|
import widgets from "@/widgets";
|
||||||
import directives from "@/directives";
|
import directives from "@/directives";
|
||||||
import components from "@/components";
|
import components from "@/components";
|
||||||
import { version, ui, lang, host } from "@/config";
|
import { lang, ui, version } from "@/config";
|
||||||
import { applyTheme } from "@/scripts/theme";
|
import { applyTheme } from "@/scripts/theme";
|
||||||
import { isDeviceDarkmode } from "@/scripts/is-device-darkmode";
|
import { isDeviceDarkmode } from "@/scripts/is-device-darkmode";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { confirm, alert, post, popup, toast } from "@/os";
|
import { alert, confirm, popup, post, toast } from "@/os";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import * as sound from "@/scripts/sound";
|
import * as sound from "@/scripts/sound";
|
||||||
import { $i, refreshAccount, login, updateAccount, signout } from "@/account";
|
import { $i, login, refreshAccount, signout, updateAccount } from "@/account";
|
||||||
import { defaultStore, ColdDeviceStorage } from "@/store";
|
import { ColdDeviceStorage, defaultStore } from "@/store";
|
||||||
import { fetchInstance, instance } from "@/instance";
|
import { fetchInstance, instance } from "@/instance";
|
||||||
import { makeHotkey } from "@/scripts/hotkey";
|
import { makeHotkey } from "@/scripts/hotkey";
|
||||||
import { search } from "@/scripts/search";
|
import { search } from "@/scripts/search";
|
||||||
|
@ -51,6 +43,13 @@ import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
import { getUrlWithoutLoginId } from "@/scripts/login-id";
|
import { getUrlWithoutLoginId } from "@/scripts/login-id";
|
||||||
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
||||||
|
|
||||||
|
const accounts = localStorage.getItem("accounts");
|
||||||
|
if (accounts) {
|
||||||
|
set("accounts", JSON.parse(accounts));
|
||||||
|
localStorage.removeItem("accounts");
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
function checkForSplash() {
|
function checkForSplash() {
|
||||||
const splash = document.getElementById("splash");
|
const splash = document.getElementById("splash");
|
||||||
// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
|
// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
|
||||||
|
@ -64,7 +63,7 @@ function checkForSplash() {
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
console.info(`Calckey v${version}`);
|
console.info(`Magnetar v${version}`);
|
||||||
|
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
console.warn("Development mode!!!");
|
console.warn("Development mode!!!");
|
||||||
|
|
|
@ -13,9 +13,12 @@ import { MenuItem } from "@/types/menu";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "./i18n";
|
import { i18n } from "./i18n";
|
||||||
import {
|
import {
|
||||||
|
BackendApiEndpoint,
|
||||||
FrontendApiEndpoint,
|
FrontendApiEndpoint,
|
||||||
FrontendApiEndpoints,
|
FrontendApiEndpoints,
|
||||||
} from "magnetar-common/built/fe-api";
|
MagApiClient,
|
||||||
|
Method,
|
||||||
|
} from "magnetar-common";
|
||||||
|
|
||||||
export const pendingApiRequestsCount = ref(0);
|
export const pendingApiRequestsCount = ref(0);
|
||||||
|
|
||||||
|
@ -23,6 +26,39 @@ const apiClient = new Misskey.api.APIClient({
|
||||||
origin: url,
|
origin: url,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const magnetarApiClient = new MagApiClient(`${url}/mag/v1`);
|
||||||
|
|
||||||
|
export async function magApi<
|
||||||
|
T extends BackendApiEndpoint<
|
||||||
|
T["method"] & Method,
|
||||||
|
T["pathParams"] & string[],
|
||||||
|
T["request"],
|
||||||
|
T["response"]
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
endpoint: T,
|
||||||
|
data: T["request"],
|
||||||
|
pathParams: {
|
||||||
|
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
|
||||||
|
| string
|
||||||
|
| number;
|
||||||
|
},
|
||||||
|
token?: string | null | undefined
|
||||||
|
): Promise<T["response"]> {
|
||||||
|
pendingApiRequestsCount.value++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await magnetarApiClient.call(
|
||||||
|
endpoint,
|
||||||
|
data,
|
||||||
|
pathParams,
|
||||||
|
token || $i?.token
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
pendingApiRequestsCount.value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function feApi<T extends keyof FrontendApiEndpoints & string>(
|
export async function feApi<T extends keyof FrontendApiEndpoints & string>(
|
||||||
endpointDef: FrontendApiEndpoint<
|
endpointDef: FrontendApiEndpoint<
|
||||||
FrontendApiEndpoints[T]["method"],
|
FrontendApiEndpoints[T]["method"],
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
<div class="note _gap">
|
<div class="note _gap">
|
||||||
<MkRemoteCaution
|
<MkRemoteCaution
|
||||||
v-if="appearNote.user.host != null"
|
v-if="appearNote.user.host != null"
|
||||||
:href="appearNote.url ?? appearNote.uri"
|
:href="(appearNote.url ?? appearNote.uri) !"
|
||||||
/>
|
/>
|
||||||
<XNoteDetailed
|
<XNoteDetailed
|
||||||
:key="appearNote.id"
|
:key="appearNote.id"
|
||||||
|
@ -70,27 +70,28 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch } from "vue";
|
import { computed, watch } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
||||||
import XNoteDetailed from "@/components/MkNoteDetailed.vue";
|
|
||||||
import XNotes from "@/components/MkNotes.vue";
|
import XNotes from "@/components/MkNotes.vue";
|
||||||
import MkRemoteCaution from "@/components/MkRemoteCaution.vue";
|
import MkRemoteCaution from "@/components/MkRemoteCaution.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
import { magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = $ref<null | misskey.entities.Note>();
|
let note = $ref<null | packed.PackNoteMaybeFull>();
|
||||||
let hasPrev = $ref(false);
|
let hasPrev = $ref(false);
|
||||||
let hasNext = $ref(false);
|
let hasNext = $ref(false);
|
||||||
let showPrev = $ref(false);
|
let showPrev = $ref(false);
|
||||||
let showNext = $ref(false);
|
let showNext = $ref(false);
|
||||||
let error = $ref();
|
let error = $ref();
|
||||||
let isRenote = $ref(false);
|
let isRenote = $ref(false);
|
||||||
let appearNote = $ref<null | misskey.entities.Note>();
|
let appearNote = $ref<null | packed.PackNoteMaybeFull>();
|
||||||
|
|
||||||
const prevPagination = {
|
const prevPagination = {
|
||||||
endpoint: "users/notes" as const,
|
endpoint: "users/notes" as const,
|
||||||
|
@ -98,7 +99,7 @@ const prevPagination = {
|
||||||
params: computed(() =>
|
params: computed(() =>
|
||||||
appearNote
|
appearNote
|
||||||
? {
|
? {
|
||||||
userId: appearNote.userId,
|
userId: appearNote.user.id,
|
||||||
untilId: appearNote.id,
|
untilId: appearNote.id,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
@ -112,7 +113,7 @@ const nextPagination = {
|
||||||
params: computed(() =>
|
params: computed(() =>
|
||||||
appearNote
|
appearNote
|
||||||
? {
|
? {
|
||||||
userId: appearNote.userId,
|
userId: appearNote.user.id,
|
||||||
sinceId: appearNote.id,
|
sinceId: appearNote.id,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
@ -125,28 +126,30 @@ function fetchNote() {
|
||||||
showPrev = false;
|
showPrev = false;
|
||||||
showNext = false;
|
showNext = false;
|
||||||
note = null;
|
note = null;
|
||||||
os.api("notes/show", {
|
os.magApi(
|
||||||
noteId: props.noteId,
|
endpoints.GetNoteById,
|
||||||
})
|
{ context: true, attachments: true },
|
||||||
|
{ id: props.noteId }
|
||||||
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
note = res;
|
note = res;
|
||||||
isRenote =
|
isRenote =
|
||||||
note.renote != null &&
|
note.renoted_note_id != null &&
|
||||||
note.text == null &&
|
note.text == null &&
|
||||||
note.fileIds.length === 0 &&
|
note.file_ids.length === 0 &&
|
||||||
note.poll == null;
|
note.poll == null;
|
||||||
appearNote = isRenote
|
appearNote = isRenote
|
||||||
? (note.renote as misskey.entities.Note)
|
? (note.renoted_note as packed.PackNoteMaybeFull)
|
||||||
: note;
|
: note;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
os.api("users/notes", {
|
os.api("users/notes", {
|
||||||
userId: note.userId,
|
userId: note.user.id,
|
||||||
untilId: note.id,
|
untilId: note.id,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}),
|
}),
|
||||||
os.api("users/notes", {
|
os.api("users/notes", {
|
||||||
userId: note.userId,
|
userId: note.user.id,
|
||||||
sinceId: note.id,
|
sinceId: note.id,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}),
|
}),
|
||||||
|
@ -173,15 +176,14 @@ definePageMetadata(
|
||||||
appearNote
|
appearNote
|
||||||
? {
|
? {
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user: appearNote.user.name || appearNote.user.username,
|
user: magTransUsername(appearNote.user),
|
||||||
}),
|
}),
|
||||||
subtitle: new Date(appearNote.createdAt).toLocaleString(),
|
subtitle: new Date(appearNote.created_at).toLocaleString(),
|
||||||
avatar: appearNote.user,
|
avatar: appearNote.user,
|
||||||
path: `/notes/${appearNote.id}`,
|
path: `/notes/${appearNote.id}`,
|
||||||
share: {
|
share: {
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user:
|
user: magTransUsername(appearNote.user),
|
||||||
appearNote.user.name || appearNote.user.username,
|
|
||||||
}),
|
}),
|
||||||
text: appearNote.text,
|
text: appearNote.text,
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,14 +35,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import XContainer from "../page-editor.container.vue";
|
import XContainer from "../page-editor.container.vue";
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
import MkSwitch from "@/components/form/switch.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNote from "@/components/MkNote.vue";
|
||||||
import XNoteDetailed from "@/components/MkNoteDetailed.vue";
|
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -56,21 +57,32 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let id: any = $ref(props.value.note);
|
let id = ref<string | null>(props.value.note);
|
||||||
let note: any = $ref(null);
|
let note = ref<packed.PackNoteMaybeFull | null>(null);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
id,
|
id,
|
||||||
async () => {
|
async () => {
|
||||||
if (id && (id.startsWith("http://") || id.startsWith("https://"))) {
|
if (
|
||||||
props.value.note = (id.endsWith("/") ? id.slice(0, -1) : id)
|
id.value &&
|
||||||
|
(id.value.startsWith("http://") || id.value.startsWith("https://"))
|
||||||
|
) {
|
||||||
|
props.value.note = (
|
||||||
|
id.value.endsWith("/") ? id.value.slice(0, -1) : id.value
|
||||||
|
)
|
||||||
.split("/")
|
.split("/")
|
||||||
.pop();
|
.pop();
|
||||||
} else {
|
} else {
|
||||||
props.value.note = id;
|
props.value.note = id.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
note = await os.api("notes/show", { noteId: props.value.note });
|
if (props.value.note) return;
|
||||||
|
|
||||||
|
note.value = await os.magApi(
|
||||||
|
endpoints.GetNoteById,
|
||||||
|
{ context: true, attachments: true },
|
||||||
|
{ id: props.value.note }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|
|
@ -125,7 +125,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, watch } from "vue";
|
import { defineAsyncComponent, watch } from "vue";
|
||||||
import XDraggable from "vuedraggable";
|
import XDraggable from "vuedraggable";
|
||||||
import FormInput from "@/components/form/input.vue";
|
|
||||||
import FormRadios from "@/components/form/radios.vue";
|
import FormRadios from "@/components/form/radios.vue";
|
||||||
import FromSlot from "@/components/form/slot.vue";
|
import FromSlot from "@/components/form/slot.vue";
|
||||||
import FormButton from "@/components/MkButton.vue";
|
import FormButton from "@/components/MkButton.vue";
|
||||||
|
@ -135,7 +134,6 @@ import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
import { unisonReload } from "@/scripts/unison-reload";
|
import { unisonReload } from "@/scripts/unison-reload";
|
||||||
|
|
||||||
async function reloadAsk() {
|
async function reloadAsk() {
|
||||||
|
@ -148,7 +146,7 @@ async function reloadAsk() {
|
||||||
unisonReload();
|
unisonReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reactions = $ref(deepClone(defaultStore.state.reactions));
|
let reactions = $ref(structuredClone(defaultStore.state.reactions));
|
||||||
|
|
||||||
const reactionPickerSkinTone = $computed(
|
const reactionPickerSkinTone = $computed(
|
||||||
defaultStore.makeGetterSetter("reactionPickerSkinTone")
|
defaultStore.makeGetterSetter("reactionPickerSkinTone")
|
||||||
|
@ -211,7 +209,7 @@ async function setDefault() {
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
reactions = deepClone(defaultStore.def.reactions.default);
|
reactions = structuredClone(defaultStore.def.reactions.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseEmoji(ev: MouseEvent) {
|
function chooseEmoji(ev: MouseEvent) {
|
||||||
|
|
|
@ -142,17 +142,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref, watch } from "vue";
|
import { reactive, watch } from "vue";
|
||||||
import FormSelect from "@/components/form/select.vue";
|
import FormSelect from "@/components/form/select.vue";
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
import MkSwitch from "@/components/form/switch.vue";
|
||||||
import FormRadios from "@/components/form/radios.vue";
|
import FormRadios from "@/components/form/radios.vue";
|
||||||
import FormButton from "@/components/MkButton.vue";
|
import FormButton from "@/components/MkButton.vue";
|
||||||
import FormRange from "@/components/form/range.vue";
|
import FormRange from "@/components/form/range.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
_id: string;
|
_id: string;
|
||||||
|
@ -160,7 +158,9 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const statusbar = reactive(
|
const statusbar = reactive(
|
||||||
deepClone(defaultStore.state.statusbars.find((x) => x.id === props._id))
|
structuredClone(
|
||||||
|
defaultStore.state.statusbars.find((x) => x.id === props._id)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -197,8 +197,8 @@ async function save() {
|
||||||
const i = defaultStore.state.statusbars.findIndex(
|
const i = defaultStore.state.statusbars.findIndex(
|
||||||
(x) => x.id === props._id
|
(x) => x.id === props._id
|
||||||
);
|
);
|
||||||
const statusbars = deepClone(defaultStore.state.statusbars);
|
const statusbars = structuredClone(defaultStore.state.statusbars);
|
||||||
statusbars[i] = deepClone(statusbar);
|
statusbars[i] = structuredClone(statusbar);
|
||||||
defaultStore.set("statusbars", statusbars);
|
defaultStore.set("statusbars", statusbars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ function registerPostFormAction({ pluginId, title, handler }) {
|
||||||
postFormActions.push({
|
postFormActions.push({
|
||||||
title,
|
title,
|
||||||
handler: (form, update) => {
|
handler: (form, update) => {
|
||||||
pluginContexts.get(pluginId).execFn(handler, [
|
pluginContexts.get(pluginId)?.execFn(handler, [
|
||||||
utils.jsToVal(form),
|
utils.jsToVal(form),
|
||||||
values.FN_NATIVE(([key, value]) => {
|
values.FN_NATIVE(([key, value]) => {
|
||||||
update(key.value, value.value);
|
update(key.value, value.value);
|
||||||
|
@ -149,7 +149,9 @@ function registerUserAction({ pluginId, title, handler }) {
|
||||||
userActions.push({
|
userActions.push({
|
||||||
title,
|
title,
|
||||||
handler: (user) => {
|
handler: (user) => {
|
||||||
pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(user)]);
|
pluginContexts
|
||||||
|
.get(pluginId)
|
||||||
|
?.execFn(handler, [utils.jsToVal(user)]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -158,7 +160,9 @@ function registerNoteAction({ pluginId, title, handler }) {
|
||||||
noteActions.push({
|
noteActions.push({
|
||||||
title,
|
title,
|
||||||
handler: (note) => {
|
handler: (note) => {
|
||||||
pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]);
|
pluginContexts
|
||||||
|
.get(pluginId)
|
||||||
|
?.execFn(handler, [utils.jsToVal(note)]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
import { packed, types } from "magnetar-common";
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/50375286 Evil magic
|
||||||
|
type Dist<U> = U extends any ? (k: U) => void : never;
|
||||||
|
|
||||||
|
type UnionToIntersection<U> = Dist<U> extends (k: infer I) => void ? I : never;
|
||||||
|
// End of evil magic
|
||||||
|
|
||||||
|
type UnionMerged<A> = { [AK in keyof A]: AK extends keyof A ? A[AK] : never };
|
||||||
|
type UnionIntersectionMerge<A> = Omit<
|
||||||
|
UnionToIntersection<A>,
|
||||||
|
keyof UnionMerged<A>
|
||||||
|
> &
|
||||||
|
UnionMerged<A>;
|
||||||
|
|
||||||
|
export function magTransProperty<
|
||||||
|
A extends Record<string, any>,
|
||||||
|
AA extends keyof UnionToIntersection<A> & string,
|
||||||
|
BB extends keyof UnionToIntersection<A> & string
|
||||||
|
>(
|
||||||
|
x: A,
|
||||||
|
keyA: AA,
|
||||||
|
keyB: BB
|
||||||
|
): UnionIntersectionMerge<A>[AA] | UnionIntersectionMerge<A>[BB] {
|
||||||
|
const a = x[keyA];
|
||||||
|
|
||||||
|
if (typeof a !== "undefined") {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return x[keyB];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magTransMap<
|
||||||
|
A extends Record<string, any>,
|
||||||
|
AA extends keyof UnionIntersectionMerge<A> & string,
|
||||||
|
BB extends keyof UnionIntersectionMerge<A> & string,
|
||||||
|
X extends any
|
||||||
|
>(
|
||||||
|
x: A,
|
||||||
|
keyA: AA,
|
||||||
|
keyB: BB,
|
||||||
|
mapLeft: (a: UnionIntersectionMerge<A>[AA]) => X = (a) => a as X,
|
||||||
|
mapRight: (b: UnionIntersectionMerge<A>[BB]) => X = (a) => a as X
|
||||||
|
): X {
|
||||||
|
const a = x[keyA];
|
||||||
|
|
||||||
|
if (typeof a !== "undefined") {
|
||||||
|
return mapLeft(a as UnionIntersectionMerge<A>[AA]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = x[keyB];
|
||||||
|
return typeof b === "undefined"
|
||||||
|
? b
|
||||||
|
: mapRight(b as UnionIntersectionMerge<A>[BB]);
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserUnion = packed.PackUserBase | Misskey.entities.User;
|
||||||
|
|
||||||
|
export function magTransUsername(x: UserUnion): string {
|
||||||
|
return (
|
||||||
|
(x as UnionToIntersection<UserUnion>)["display_name"] ||
|
||||||
|
(x as UnionToIntersection<UserUnion>)["name"] ||
|
||||||
|
(x as UnionToIntersection<UserUnion>)["username"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magReactionCount(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): number {
|
||||||
|
if (Array.isArray(note.reactions)) {
|
||||||
|
return Number(
|
||||||
|
note.reactions
|
||||||
|
.map(([, cnt]) => cnt)
|
||||||
|
.reduce((partialSum, val) => partialSum + val, 0)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Object.values(note).reduce((accum, val) => accum + val, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magHasReacted(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): boolean {
|
||||||
|
if (Array.isArray(note.reactions)) {
|
||||||
|
return note.reactions.some(([, , reacted]) => reacted === true);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
typeof (note as Misskey.entities.Note).myReaction !== "undefined" &&
|
||||||
|
(note as Misskey.entities.Note).myReaction !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magReactionSelf(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): string | null {
|
||||||
|
if (Array.isArray(note.reactions)) {
|
||||||
|
const found = note.reactions.find(
|
||||||
|
([, , reacted]) => reacted === true
|
||||||
|
)?.[0];
|
||||||
|
return typeof found !== "undefined" ? magReactionToLegacy(found) : null;
|
||||||
|
} else if ((note as Misskey.entities.Note).myReaction !== "undefined") {
|
||||||
|
return (note as Misskey.entities.Note).myReaction ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function noteIsMag(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): note is packed.PackNoteMaybeFull {
|
||||||
|
return "created_at" in note;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magIsRenote(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
magTransProperty(note, "renoted_note", "renote") != null &&
|
||||||
|
note.text == null &&
|
||||||
|
magTransProperty(note, "file_ids", "fileIds").length === 0 &&
|
||||||
|
note.poll == null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magEffectiveNote(
|
||||||
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
|
): packed.PackNoteMaybeFull | Misskey.entities.Note {
|
||||||
|
return magIsRenote(note)
|
||||||
|
? (magTransProperty(note, "renoted_note", "renote") as
|
||||||
|
| packed.PackNoteMaybeFull
|
||||||
|
| Misskey.entities.Note)
|
||||||
|
: note;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magLegacyVisibility(
|
||||||
|
vis: types.NoteVisibility | Misskey.entities.Note["visibility"]
|
||||||
|
): Misskey.entities.Note["visibility"];
|
||||||
|
|
||||||
|
export function magLegacyVisibility(vis: undefined): undefined;
|
||||||
|
|
||||||
|
export function magLegacyVisibility(
|
||||||
|
vis: types.NoteVisibility | Misskey.entities.Note["visibility"] | undefined
|
||||||
|
): Misskey.entities.Note["visibility"] | undefined {
|
||||||
|
if (typeof vis === "undefined") return vis;
|
||||||
|
|
||||||
|
switch (vis) {
|
||||||
|
case "Public":
|
||||||
|
return "public";
|
||||||
|
case "Home":
|
||||||
|
return "home";
|
||||||
|
case "Followers":
|
||||||
|
return "followers";
|
||||||
|
case "Direct":
|
||||||
|
return "specified";
|
||||||
|
|
||||||
|
case "public":
|
||||||
|
case "home":
|
||||||
|
case "followers":
|
||||||
|
case "specified":
|
||||||
|
return vis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magConvertReaction(
|
||||||
|
reaction: string,
|
||||||
|
urlHint?: string | null
|
||||||
|
): types.Reaction {
|
||||||
|
if (reaction.endsWith("@.:")) {
|
||||||
|
reaction = reaction.replaceAll(/@\.:$/, ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reaction.match(/^:.+:$/)) {
|
||||||
|
const [name, host] = reaction.split("@");
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
host: host || null,
|
||||||
|
url: urlHint!,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magReactionToLegacy(reaction: types.Reaction | string): string {
|
||||||
|
if (typeof reaction === "string") {
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("name" in reaction) {
|
||||||
|
if (reaction.host) {
|
||||||
|
return `:${reaction.name}@${reaction.host}:`;
|
||||||
|
} else {
|
||||||
|
return `:${reaction.name}@.:`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("raw" in reaction) {
|
||||||
|
return reaction.raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magReactionPairToLegacy(
|
||||||
|
reaction: types.ReactionPair | [string, number]
|
||||||
|
): [string, number] {
|
||||||
|
const legacy = magReactionToLegacy(reaction[0]);
|
||||||
|
return [legacy, reaction[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magReactionIndex(
|
||||||
|
reactions: types.ReactionPair[],
|
||||||
|
reactionType: types.Reaction
|
||||||
|
) {
|
||||||
|
return reactions.findIndex(([r, ,]) => {
|
||||||
|
if (typeof r !== typeof reactionType) return false;
|
||||||
|
|
||||||
|
if (typeof r === "string") {
|
||||||
|
return r === reactionType;
|
||||||
|
} else if (
|
||||||
|
"name" in r &&
|
||||||
|
"name" in (reactionType as { name: string; host: string | null })
|
||||||
|
) {
|
||||||
|
const { name, host } = reactionType as {
|
||||||
|
name: string;
|
||||||
|
host: string | null;
|
||||||
|
};
|
||||||
|
const { name: rName, host: rHost } = r;
|
||||||
|
|
||||||
|
return name === rName && (host ?? null) === (rHost ?? null);
|
||||||
|
} else if ("raw" in r && "raw" in (reactionType as { raw: string })) {
|
||||||
|
return r.raw === (reactionType as { raw: string }).raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,23 +1,28 @@
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
import { magTransMap, magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
export type Muted = {
|
export type Muted = {
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
matched: string[];
|
matched: string[];
|
||||||
what?: string; // "note" || "reply" || "renote" || "quote"
|
what?: "note" | "reply" | "renote" | "quote";
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotMuted = { muted: false, matched: [] };
|
export type NotMuted = { muted: false; matched: string[] };
|
||||||
|
|
||||||
function checkWordMute(
|
function checkWordMute(
|
||||||
note: NoteLike,
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note,
|
||||||
mutedWords: Array<string | string[]>
|
mutedWords: Array<string | string[]>
|
||||||
): Muted {
|
): Muted {
|
||||||
let text = `${note.cw ?? ""} ${note.text ?? ""}`;
|
let text = `${note.cw ?? ""} ${note.text ?? ""}`;
|
||||||
if (note.files != null)
|
const attachments = magTransProperty(note, "attachments", "files");
|
||||||
text += ` ${note.files.map((f) => f.comment ?? "").join(" ")}`;
|
if (attachments)
|
||||||
|
text += ` ${attachments.map((f) => f.comment ?? "").join(" ")}`;
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
|
|
||||||
if (text === "") return NotMuted;
|
let result: Muted | NotMuted = { muted: false, matched: [] };
|
||||||
|
|
||||||
let result = { muted: false, matched: [] };
|
if (text === "") return result;
|
||||||
|
|
||||||
for (const mutePattern of mutedWords) {
|
for (const mutePattern of mutedWords) {
|
||||||
if (Array.isArray(mutePattern)) {
|
if (Array.isArray(mutePattern)) {
|
||||||
|
@ -59,13 +64,13 @@ function checkWordMute(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWordSoftMute(
|
export function getWordSoftMute(
|
||||||
note: Record<string, any>,
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note,
|
||||||
me: Record<string, any> | null | undefined,
|
me: Record<string, any> | null | undefined,
|
||||||
mutedWords: Array<string | string[]>
|
mutedWords: Array<string | string[]>
|
||||||
): Muted {
|
): Muted {
|
||||||
// 自分自身
|
// 自分自身
|
||||||
if (me && note.userId === me.id) {
|
if (me && magTransMap(note, "user", "userId", (u) => u.id) === me.id) {
|
||||||
return NotMuted;
|
return { muted: false, matched: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mutedWords.length > 0) {
|
if (mutedWords.length > 0) {
|
||||||
|
@ -75,16 +80,18 @@ export function getWordSoftMute(
|
||||||
return noteMuted;
|
return noteMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.renote) {
|
const renote = magTransProperty(note, "renoted_note", "renote");
|
||||||
let renoteMuted = checkWordMute(note.renote, mutedWords);
|
if (renote) {
|
||||||
|
let renoteMuted = checkWordMute(renote, mutedWords);
|
||||||
if (renoteMuted.muted) {
|
if (renoteMuted.muted) {
|
||||||
renoteMuted.what = note.text == null ? "renote" : "quote";
|
renoteMuted.what = note.text == null ? "renote" : "quote";
|
||||||
return renoteMuted;
|
return renoteMuted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.reply) {
|
const reply = magTransProperty(note, "parent_note", "reply");
|
||||||
let replyMuted = checkWordMute(note.reply, mutedWords);
|
if (reply) {
|
||||||
|
let replyMuted = checkWordMute(reply, mutedWords);
|
||||||
if (replyMuted.muted) {
|
if (replyMuted.muted) {
|
||||||
replyMuted.what = "reply";
|
replyMuted.what = "reply";
|
||||||
return replyMuted;
|
return replyMuted;
|
||||||
|
@ -92,5 +99,5 @@ export function getWordSoftMute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotMuted;
|
return { muted: false, matched: [] };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
// structredCloneが遅いため
|
|
||||||
// SEE: http://var.blog.jp/archives/86038606.html
|
|
||||||
|
|
||||||
type Cloneable =
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| null
|
|
||||||
| { [key: string]: Cloneable }
|
|
||||||
| Cloneable[];
|
|
||||||
|
|
||||||
export function deepClone<T extends Cloneable>(x: T): T {
|
|
||||||
if (typeof x === "object") {
|
|
||||||
if (x === null) return x;
|
|
||||||
if (Array.isArray(x)) return x.map(deepClone) as T;
|
|
||||||
const obj = {} as Record<string, Cloneable>;
|
|
||||||
for (const [k, v] of Object.entries(x)) {
|
|
||||||
obj[k] = deepClone(v);
|
|
||||||
}
|
|
||||||
return obj as T;
|
|
||||||
} else {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
export function extractAvgColorFromBlurhash(hash: string) {
|
export function extractAvgColorFromBlurhash(hash: string | null) {
|
||||||
return typeof hash === "string"
|
return hash
|
||||||
? `#${[...hash.slice(2, 6)]
|
? `#${[...hash.slice(2, 6)]
|
||||||
.map((x) =>
|
.map((x) =>
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(
|
||||||
|
|
|
@ -9,24 +9,23 @@ import { url } from "@/config";
|
||||||
import { noteActions } from "@/store";
|
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,
|
||||||
|
magTransMap,
|
||||||
|
magTransProperty,
|
||||||
|
magTransUsername,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
export function getNoteMenu(props: {
|
export function getNoteMenu(props: {
|
||||||
note: misskey.entities.Note;
|
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
||||||
menuButton: Ref<HTMLElement | undefined>;
|
menuButton: Ref<HTMLElement | undefined>;
|
||||||
translation: Ref<any>;
|
translation: Ref<any>;
|
||||||
translating: Ref<boolean>;
|
translating: Ref<boolean>;
|
||||||
isDeleted: Ref<boolean>;
|
isDeleted: Ref<boolean>;
|
||||||
currentClipPage?: Ref<misskey.entities.Clip>;
|
currentClipPage?: Ref<misskey.entities.Clip>;
|
||||||
}) {
|
}) {
|
||||||
const isRenote =
|
const appearNote = magEffectiveNote(props.note);
|
||||||
props.note.renote != null &&
|
|
||||||
props.note.text == null &&
|
|
||||||
props.note.fileIds.length === 0 &&
|
|
||||||
props.note.poll == null;
|
|
||||||
|
|
||||||
const appearNote = isRenote
|
|
||||||
? (props.note.renote as misskey.entities.Note)
|
|
||||||
: props.note;
|
|
||||||
|
|
||||||
function del(): void {
|
function del(): void {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
|
@ -54,8 +53,8 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
os.post({
|
os.post({
|
||||||
initialNote: appearNote,
|
initialNote: appearNote,
|
||||||
renote: appearNote.renote,
|
renote: magTransProperty(appearNote, "renoted_note", "renote"),
|
||||||
reply: appearNote.reply,
|
reply: magTransProperty(appearNote, "parent_note", "reply"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,8 +62,8 @@ export function getNoteMenu(props: {
|
||||||
function edit(): void {
|
function edit(): void {
|
||||||
os.post({
|
os.post({
|
||||||
initialNote: appearNote,
|
initialNote: appearNote,
|
||||||
renote: appearNote.renote,
|
renote: magTransProperty(appearNote, "renoted_note", "renote"),
|
||||||
reply: appearNote.reply,
|
reply: magTransProperty(appearNote, "parent_note", "reply"),
|
||||||
editId: appearNote.id,
|
editId: appearNote.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -228,23 +227,12 @@ export function getNoteMenu(props: {
|
||||||
props.isDeleted.value = true;
|
props.isDeleted.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promote(): Promise<void> {
|
|
||||||
const { canceled, result: days } = await os.inputNumber({
|
|
||||||
title: i18n.ts.numberOfDays,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
os.apiWithDialog("admin/promo/create", {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
expiresAt: Date.now() + 86400000 * days,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function share(): void {
|
function share(): void {
|
||||||
navigator.share({
|
navigator.share({
|
||||||
title: i18n.t("noteOf", { user: appearNote.user.name }),
|
title: i18n.t("noteOf", {
|
||||||
text: appearNote.text,
|
user: magTransUsername(appearNote.user),
|
||||||
|
}),
|
||||||
|
text: appearNote.text ?? undefined,
|
||||||
url: `${url}/notes/${appearNote.id}`,
|
url: `${url}/notes/${appearNote.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -266,7 +254,8 @@ export function getNoteMenu(props: {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAppearAuthor = appearNote.userId === $i.id;
|
const isAppearAuthor =
|
||||||
|
magTransMap(appearNote, "user", "userId", (u) => u.id) === $i.id;
|
||||||
const isModerator = $i.isAdmin || $i.isModerator;
|
const isModerator = $i.isAdmin || $i.isModerator;
|
||||||
|
|
||||||
menu = [
|
menu = [
|
||||||
|
@ -353,7 +342,7 @@ export function getNoteMenu(props: {
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
appearNote.url || appearNote.uri,
|
(appearNote.url || appearNote.uri)!,
|
||||||
"_blank"
|
"_blank"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -468,7 +457,7 @@ export function getNoteMenu(props: {
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open(
|
||||||
appearNote.url || appearNote.uri,
|
(appearNote.url || appearNote.uri)!,
|
||||||
"_blank"
|
"_blank"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { i18n } from "@/i18n";
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 投稿を表す文字列を取得します。
|
* 投稿を表す文字列を取得します。
|
||||||
* @param {*} note (packされた)投稿
|
* @param {*} note (packされた)投稿
|
||||||
*/
|
*/
|
||||||
export const getNoteSummary = (note: misskey.entities.Note): string => {
|
export const getNoteSummary = (
|
||||||
|
note: packed.PackNoteMaybeFull | misskey.entities.Note
|
||||||
|
): string => {
|
||||||
/*
|
/*
|
||||||
if (note.deletedAt) {
|
if (note.deletedAt) {
|
||||||
return `(${i18n.ts.deletedNote})`;
|
return `(${i18n.ts.deletedNote})`;
|
||||||
|
@ -22,8 +25,9 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ファイルが添付されているとき
|
// ファイルが添付されているとき
|
||||||
if ((note.files || []).length !== 0) {
|
const files = magTransProperty(note, "attachments", "files");
|
||||||
const len = note.files?.length;
|
if ((files || []).length !== 0) {
|
||||||
|
const len = files?.length;
|
||||||
summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
|
summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
ref,
|
ref,
|
||||||
Ref,
|
Ref,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
export const setPageMetadata = Symbol("setPageMetadata");
|
export const setPageMetadata = Symbol("setPageMetadata");
|
||||||
export const pageMetadataProvider = Symbol("pageMetadataProvider");
|
export const pageMetadataProvider = Symbol("pageMetadataProvider");
|
||||||
|
@ -17,7 +18,7 @@ export type PageMetadata = {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
avatar?: misskey.entities.User | null;
|
avatar?: packed.PackUserBase | misskey.entities.User | null;
|
||||||
userName?: misskey.entities.User | null;
|
userName?: misskey.entities.User | null;
|
||||||
bg?: string;
|
bg?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
import { globalEvents } from "@/events";
|
import { globalEvents } from "@/events";
|
||||||
|
import lightTheme from "@/themes/_light.json5";
|
||||||
|
import darkTheme from "@/themes/_dark.json5";
|
||||||
|
|
||||||
export type Theme = {
|
export type Theme = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -11,10 +13,6 @@ export type Theme = {
|
||||||
props: Record<string, string>;
|
props: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
import lightTheme from "@/themes/_light.json5";
|
|
||||||
import darkTheme from "@/themes/_dark.json5";
|
|
||||||
import { deepClone } from "./clone";
|
|
||||||
|
|
||||||
export const themeProps = Object.keys(lightTheme.props).filter(
|
export const themeProps = Object.keys(lightTheme.props).filter(
|
||||||
(key) => !key.startsWith("X")
|
(key) => !key.startsWith("X")
|
||||||
);
|
);
|
||||||
|
@ -77,7 +75,7 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
const colorSchema = theme.base === "dark" ? "dark" : "light";
|
const colorSchema = theme.base === "dark" ? "dark" : "light";
|
||||||
|
|
||||||
// Deep copy
|
// Deep copy
|
||||||
const _theme = deepClone(theme);
|
const _theme = structuredClone(theme);
|
||||||
|
|
||||||
if (_theme.base) {
|
if (_theme.base) {
|
||||||
const base = [lightTheme, darkTheme].find((x) => x.id === _theme.base);
|
const base = [lightTheme, darkTheme].find((x) => x.id === _theme.base);
|
||||||
|
|
|
@ -3,10 +3,16 @@ import * as misskey from "calckey-js";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magConvertReaction,
|
||||||
|
magReactionIndex,
|
||||||
|
noteIsMag,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
export function useNoteCapture(props: {
|
export function useNoteCapture(props: {
|
||||||
rootEl: Ref<HTMLElement>;
|
rootEl: Ref<HTMLElement>;
|
||||||
note: Ref<misskey.entities.Note>;
|
note: Ref<packed.PackNoteMaybeFull | misskey.entities.Note>;
|
||||||
isDeletedRef: Ref<boolean>;
|
isDeletedRef: Ref<boolean>;
|
||||||
}) {
|
}) {
|
||||||
const note = props.note;
|
const note = props.note;
|
||||||
|
@ -17,80 +23,188 @@ export function useNoteCapture(props: {
|
||||||
|
|
||||||
if (id !== note.value.id) return;
|
if (id !== note.value.id) return;
|
||||||
|
|
||||||
switch (type) {
|
if (noteIsMag(note.value)) {
|
||||||
case "reacted": {
|
switch (type) {
|
||||||
const reaction = body.reaction;
|
case "reacted": {
|
||||||
|
const reaction = body.reaction as string;
|
||||||
|
|
||||||
if (body.emoji) {
|
if (body.emoji) {
|
||||||
const emojis = note.value.emojis || [];
|
const emojis = note.value.emojis || [];
|
||||||
if (!emojis.includes(body.emoji)) {
|
if (!emojis.includes(body.emoji)) {
|
||||||
note.value.emojis = [...emojis, body.emoji];
|
note.value.emojis = [
|
||||||
|
...emojis,
|
||||||
|
{
|
||||||
|
id: body.emoji.id,
|
||||||
|
shortcode: body.emoji.name,
|
||||||
|
url: body.emoji.url,
|
||||||
|
width: body.emoji.width ?? null,
|
||||||
|
height: body.emoji.height ?? null,
|
||||||
|
category: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reactionType = magConvertReaction(reaction);
|
||||||
|
const foundReaction = magReactionIndex(
|
||||||
|
note.value.reactions,
|
||||||
|
reactionType
|
||||||
|
);
|
||||||
|
|
||||||
|
const selfReact = ($i && body.userId === $i.id) || false;
|
||||||
|
if (foundReaction >= 0) {
|
||||||
|
note.value.reactions[foundReaction] = [
|
||||||
|
note.value.reactions[foundReaction][0],
|
||||||
|
note.value.reactions[foundReaction][1] + 1,
|
||||||
|
selfReact,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
note.value.reactions.push([reactionType, 1, selfReact]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
case "unreacted": {
|
||||||
const currentCount = note.value.reactions?.[reaction] || 0;
|
const reaction = body.reaction;
|
||||||
|
|
||||||
note.value.reactions[reaction] = currentCount + 1;
|
const reactionType = magConvertReaction(reaction);
|
||||||
|
const foundReaction = magReactionIndex(
|
||||||
|
note.value.reactions,
|
||||||
|
reactionType
|
||||||
|
);
|
||||||
|
|
||||||
if ($i && body.userId === $i.id) {
|
if (foundReaction >= 0) {
|
||||||
note.value.myReaction = reaction;
|
const cnt = note.value.reactions[foundReaction][1];
|
||||||
}
|
note.value.reactions[foundReaction] = [
|
||||||
break;
|
note.value.reactions[foundReaction][0],
|
||||||
}
|
cnt === 0 ? 0 : cnt - 1,
|
||||||
|
false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
case "unreacted": {
|
break;
|
||||||
const reaction = body.reaction;
|
|
||||||
|
|
||||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
|
||||||
const currentCount = note.value.reactions?.[reaction] || 0;
|
|
||||||
|
|
||||||
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
|
|
||||||
|
|
||||||
if ($i && body.userId === $i.id) {
|
|
||||||
note.value.myReaction = undefined;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "pollVoted": {
|
|
||||||
const choice = body.choice;
|
|
||||||
|
|
||||||
if (note.value.poll) {
|
|
||||||
const choices = [...note.value.poll.choices];
|
|
||||||
choices[choice] = {
|
|
||||||
...choices[choice],
|
|
||||||
votes: choices[choice].votes + 1,
|
|
||||||
...($i && body.userId === $i.id
|
|
||||||
? {
|
|
||||||
isVoted: true,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
note.value.poll.choices = choices;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
case "pollVoted": {
|
||||||
|
const choice = body.choice;
|
||||||
|
|
||||||
|
if (note.value.poll) {
|
||||||
|
const options = [...note.value.poll.options];
|
||||||
|
options[choice] = {
|
||||||
|
...options[choice],
|
||||||
|
votes_count: options[choice].votes_count + 1,
|
||||||
|
voted: ($i && body.userId === $i.id) || null,
|
||||||
|
};
|
||||||
|
note.value.poll.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "deleted": {
|
||||||
|
props.isDeletedRef.value = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "updated": {
|
||||||
|
const editedNote = await os.magApi(
|
||||||
|
endpoints.GetNoteById,
|
||||||
|
{
|
||||||
|
attachments: true,
|
||||||
|
context: true,
|
||||||
|
},
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = new Set<string>();
|
||||||
|
Object.keys(editedNote)
|
||||||
|
.concat(Object.keys(note.value))
|
||||||
|
.forEach((key) => keys.add(key));
|
||||||
|
keys.forEach((key) => {
|
||||||
|
note.value[key] = editedNote[key];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
switch (type) {
|
||||||
|
case "reacted": {
|
||||||
|
const reaction = body.reaction;
|
||||||
|
|
||||||
case "deleted": {
|
if (body.emoji) {
|
||||||
props.isDeletedRef.value = true;
|
const emojis = note.value.emojis || [];
|
||||||
break;
|
if (!emojis.includes(body.emoji)) {
|
||||||
}
|
note.value.emojis = [...emojis, body.emoji];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case "updated": {
|
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||||
const editedNote = await os.api("notes/show", {
|
const currentCount = note.value.reactions?.[reaction] || 0;
|
||||||
noteId: id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const keys = new Set<string>();
|
note.value.reactions[reaction] = currentCount + 1;
|
||||||
Object.keys(editedNote)
|
|
||||||
.concat(Object.keys(note.value))
|
if ($i && body.userId === $i.id) {
|
||||||
.forEach((key) => keys.add(key));
|
note.value.myReaction = reaction;
|
||||||
keys.forEach((key) => {
|
}
|
||||||
note.value[key] = editedNote[key];
|
break;
|
||||||
});
|
}
|
||||||
break;
|
|
||||||
|
case "unreacted": {
|
||||||
|
const reaction = body.reaction;
|
||||||
|
|
||||||
|
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||||
|
const currentCount = note.value.reactions?.[reaction] || 0;
|
||||||
|
|
||||||
|
note.value.reactions[reaction] = Math.max(
|
||||||
|
0,
|
||||||
|
currentCount - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($i && body.userId === $i.id) {
|
||||||
|
note.value.myReaction = undefined;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "pollVoted": {
|
||||||
|
const choice = body.choice;
|
||||||
|
|
||||||
|
if (note.value.poll) {
|
||||||
|
const choices = [...note.value.poll.choices];
|
||||||
|
choices[choice] = {
|
||||||
|
...choices[choice],
|
||||||
|
votes: choices[choice].votes + 1,
|
||||||
|
...($i && body.userId === $i.id
|
||||||
|
? {
|
||||||
|
isVoted: true,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
note.value.poll.choices = choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "deleted": {
|
||||||
|
props.isDeletedRef.value = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "updated": {
|
||||||
|
const editedNote = await os.api("notes/show", {
|
||||||
|
noteId: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const keys = new Set<string>();
|
||||||
|
Object.keys(editedNote)
|
||||||
|
.concat(Object.keys(note.value))
|
||||||
|
.forEach((key) => keys.add(key));
|
||||||
|
keys.forEach((key) => {
|
||||||
|
note.value[key] = editedNote[key];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,16 @@ 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 = [];
|
export const postFormActions: {
|
||||||
export const userActions = [];
|
title: any;
|
||||||
export const noteActions = [];
|
handler: (form: any, update: any) => void;
|
||||||
export const noteViewInterruptors = [];
|
}[] = [];
|
||||||
export const notePostInterruptors = [];
|
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",
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { markRaw } from "vue";
|
||||||
import { notificationTypes } from "calckey-js";
|
import { notificationTypes } from "calckey-js";
|
||||||
import { Storage } from "../../pizzax";
|
import { Storage } from "../../pizzax";
|
||||||
import { api } from "@/os";
|
import { api } from "@/os";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
|
|
||||||
type ColumnWidget = {
|
type ColumnWidget = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -158,7 +157,7 @@ export function swapColumn(a: Column["id"], b: Column["id"]) {
|
||||||
const aY = deckStore.state.layout[aX].findIndex((id) => id === a);
|
const aY = deckStore.state.layout[aX].findIndex((id) => id === a);
|
||||||
const bX = deckStore.state.layout.findIndex((ids) => ids.indexOf(b) !== -1);
|
const bX = deckStore.state.layout.findIndex((ids) => ids.indexOf(b) !== -1);
|
||||||
const bY = deckStore.state.layout[bX].findIndex((id) => id === b);
|
const bY = deckStore.state.layout[bX].findIndex((id) => id === b);
|
||||||
const layout = deepClone(deckStore.state.layout);
|
const layout = structuredClone(deckStore.state.layout);
|
||||||
layout[aX][aY] = b;
|
layout[aX][aY] = b;
|
||||||
layout[bX][bY] = a;
|
layout[bX][bY] = a;
|
||||||
deckStore.set("layout", layout);
|
deckStore.set("layout", layout);
|
||||||
|
@ -166,7 +165,7 @@ export function swapColumn(a: Column["id"], b: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapLeftColumn(id: Column["id"]) {
|
export function swapLeftColumn(id: Column["id"]) {
|
||||||
const layout = deepClone(deckStore.state.layout);
|
const layout = structuredClone(deckStore.state.layout);
|
||||||
deckStore.state.layout.some((ids, i) => {
|
deckStore.state.layout.some((ids, i) => {
|
||||||
if (ids.includes(id)) {
|
if (ids.includes(id)) {
|
||||||
const left = deckStore.state.layout[i - 1];
|
const left = deckStore.state.layout[i - 1];
|
||||||
|
@ -182,7 +181,7 @@ export function swapLeftColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapRightColumn(id: Column["id"]) {
|
export function swapRightColumn(id: Column["id"]) {
|
||||||
const layout = deepClone(deckStore.state.layout);
|
const layout = structuredClone(deckStore.state.layout);
|
||||||
deckStore.state.layout.some((ids, i) => {
|
deckStore.state.layout.some((ids, i) => {
|
||||||
if (ids.includes(id)) {
|
if (ids.includes(id)) {
|
||||||
const right = deckStore.state.layout[i + 1];
|
const right = deckStore.state.layout[i + 1];
|
||||||
|
@ -198,11 +197,11 @@ export function swapRightColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapUpColumn(id: Column["id"]) {
|
export function swapUpColumn(id: Column["id"]) {
|
||||||
const layout = deepClone(deckStore.state.layout);
|
const layout = structuredClone(deckStore.state.layout);
|
||||||
const idsIndex = deckStore.state.layout.findIndex((ids) =>
|
const idsIndex = deckStore.state.layout.findIndex((ids) =>
|
||||||
ids.includes(id)
|
ids.includes(id)
|
||||||
);
|
);
|
||||||
const ids = deepClone(deckStore.state.layout[idsIndex]);
|
const ids = structuredClone(deckStore.state.layout[idsIndex]);
|
||||||
ids.some((x, i) => {
|
ids.some((x, i) => {
|
||||||
if (x === id) {
|
if (x === id) {
|
||||||
const up = ids[i - 1];
|
const up = ids[i - 1];
|
||||||
|
@ -220,11 +219,11 @@ export function swapUpColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function swapDownColumn(id: Column["id"]) {
|
export function swapDownColumn(id: Column["id"]) {
|
||||||
const layout = deepClone(deckStore.state.layout);
|
const layout = structuredClone(deckStore.state.layout);
|
||||||
const idsIndex = deckStore.state.layout.findIndex((ids) =>
|
const idsIndex = deckStore.state.layout.findIndex((ids) =>
|
||||||
ids.includes(id)
|
ids.includes(id)
|
||||||
);
|
);
|
||||||
const ids = deepClone(deckStore.state.layout[idsIndex]);
|
const ids = structuredClone(deckStore.state.layout[idsIndex]);
|
||||||
ids.some((x, i) => {
|
ids.some((x, i) => {
|
||||||
if (x === id) {
|
if (x === id) {
|
||||||
const down = ids[i + 1];
|
const down = ids[i + 1];
|
||||||
|
@ -242,7 +241,7 @@ export function swapDownColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stackLeftColumn(id: Column["id"]) {
|
export function stackLeftColumn(id: Column["id"]) {
|
||||||
let layout = deepClone(deckStore.state.layout);
|
let layout = structuredClone(deckStore.state.layout);
|
||||||
const i = deckStore.state.layout.findIndex((ids) => ids.includes(id));
|
const i = deckStore.state.layout.findIndex((ids) => ids.includes(id));
|
||||||
layout = layout.map((ids) => ids.filter((_id) => _id !== id));
|
layout = layout.map((ids) => ids.filter((_id) => _id !== id));
|
||||||
layout[i - 1].push(id);
|
layout[i - 1].push(id);
|
||||||
|
@ -252,7 +251,7 @@ export function stackLeftColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popRightColumn(id: Column["id"]) {
|
export function popRightColumn(id: Column["id"]) {
|
||||||
let layout = deepClone(deckStore.state.layout);
|
let layout = structuredClone(deckStore.state.layout);
|
||||||
const i = deckStore.state.layout.findIndex((ids) => ids.includes(id));
|
const i = deckStore.state.layout.findIndex((ids) => ids.includes(id));
|
||||||
const affected = layout[i];
|
const affected = layout[i];
|
||||||
layout = layout.map((ids) => ids.filter((_id) => _id !== id));
|
layout = layout.map((ids) => ids.filter((_id) => _id !== id));
|
||||||
|
@ -260,7 +259,7 @@ export function popRightColumn(id: Column["id"]) {
|
||||||
layout = layout.filter((ids) => ids.length > 0);
|
layout = layout.filter((ids) => ids.length > 0);
|
||||||
deckStore.set("layout", layout);
|
deckStore.set("layout", layout);
|
||||||
|
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
if (affected.includes(column.id)) {
|
if (affected.includes(column.id)) {
|
||||||
column.active = true;
|
column.active = true;
|
||||||
|
@ -272,9 +271,9 @@ export function popRightColumn(id: Column["id"]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
export function addColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
||||||
const column = deepClone(deckStore.state.columns[columnIndex]);
|
const column = structuredClone(deckStore.state.columns[columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
if (column.widgets == null) column.widgets = [];
|
if (column.widgets == null) column.widgets = [];
|
||||||
column.widgets.unshift(widget);
|
column.widgets.unshift(widget);
|
||||||
|
@ -284,9 +283,9 @@ export function addColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
export function removeColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
||||||
const column = deepClone(deckStore.state.columns[columnIndex]);
|
const column = structuredClone(deckStore.state.columns[columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
column.widgets = column.widgets.filter((w) => w.id !== widget.id);
|
column.widgets = column.widgets.filter((w) => w.id !== widget.id);
|
||||||
columns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
|
@ -295,9 +294,9 @@ export function removeColumnWidget(id: Column["id"], widget: ColumnWidget) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setColumnWidgets(id: Column["id"], widgets: ColumnWidget[]) {
|
export function setColumnWidgets(id: Column["id"], widgets: ColumnWidget[]) {
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
||||||
const column = deepClone(deckStore.state.columns[columnIndex]);
|
const column = structuredClone(deckStore.state.columns[columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
column.widgets = widgets;
|
column.widgets = widgets;
|
||||||
columns[columnIndex] = column;
|
columns[columnIndex] = column;
|
||||||
|
@ -310,9 +309,9 @@ export function updateColumnWidget(
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
widgetData: any
|
widgetData: any
|
||||||
) {
|
) {
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
||||||
const column = deepClone(deckStore.state.columns[columnIndex]);
|
const column = structuredClone(deckStore.state.columns[columnIndex]);
|
||||||
if (column == null) return;
|
if (column == null) return;
|
||||||
column.widgets = column.widgets.map((w) =>
|
column.widgets = column.widgets.map((w) =>
|
||||||
w.id === widgetId
|
w.id === widgetId
|
||||||
|
@ -328,9 +327,9 @@ export function updateColumnWidget(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateColumn(id: Column["id"], column: Partial<Column>) {
|
export function updateColumn(id: Column["id"], column: Partial<Column>) {
|
||||||
const columns = deepClone(deckStore.state.columns);
|
const columns = structuredClone(deckStore.state.columns);
|
||||||
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
const columnIndex = deckStore.state.columns.findIndex((c) => c.id === id);
|
||||||
const currentColumn = deepClone(deckStore.state.columns[columnIndex]);
|
const currentColumn = structuredClone(deckStore.state.columns[columnIndex]);
|
||||||
if (currentColumn == null) return;
|
if (currentColumn == null) return;
|
||||||
for (const [k, v] of Object.entries(column)) {
|
for (const [k, v] of Object.entries(column)) {
|
||||||
currentColumn[k] = v;
|
currentColumn[k] = v;
|
||||||
|
|
|
@ -123,20 +123,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, reactive, ref } from "vue";
|
import { onUnmounted, reactive } from "vue";
|
||||||
import {
|
import { useWidgetPropsManager, Widget, WidgetComponentExpose } from "./widget";
|
||||||
useWidgetPropsManager,
|
|
||||||
Widget,
|
|
||||||
WidgetComponentEmits,
|
|
||||||
WidgetComponentExpose,
|
|
||||||
WidgetComponentProps,
|
|
||||||
} from "./widget";
|
|
||||||
import { GetFormResultType } from "@/scripts/form";
|
import { GetFormResultType } from "@/scripts/form";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import number from "@/filters/number";
|
import number from "@/filters/number";
|
||||||
import * as sound from "@/scripts/sound";
|
import * as sound from "@/scripts/sound";
|
||||||
import * as os from "@/os";
|
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
|
|
||||||
const name = "jobQueue";
|
const name = "jobQueue";
|
||||||
|
|
||||||
|
@ -185,12 +177,12 @@ const prev = reactive({} as typeof current);
|
||||||
const jammedSound = sound.setVolume(sound.getAudio("syuilo/queue-jammed"), 1);
|
const jammedSound = sound.setVolume(sound.getAudio("syuilo/queue-jammed"), 1);
|
||||||
|
|
||||||
for (const domain of ["inbox", "deliver"]) {
|
for (const domain of ["inbox", "deliver"]) {
|
||||||
prev[domain] = deepClone(current[domain]);
|
prev[domain] = structuredClone(current[domain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onStats = (stats) => {
|
const onStats = (stats) => {
|
||||||
for (const domain of ["inbox", "deliver"]) {
|
for (const domain of ["inbox", "deliver"]) {
|
||||||
prev[domain] = deepClone(current[domain]);
|
prev[domain] = structuredClone(current[domain]);
|
||||||
current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
|
current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
|
||||||
current[domain].active = stats[domain].active;
|
current[domain].active = stats[domain].active;
|
||||||
current[domain].waiting = stats[domain].waiting;
|
current[domain].waiting = stats[domain].waiting;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { reactive, watch } from "vue";
|
import { reactive, toRaw, watch } from "vue";
|
||||||
import { throttle } from "throttle-debounce";
|
import { throttle } from "throttle-debounce";
|
||||||
import { Form, GetFormResultType } from "@/scripts/form";
|
import { Form, GetFormResultType } from "@/scripts/form";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { deepClone } from "@/scripts/clone";
|
|
||||||
|
|
||||||
export type Widget<P extends Record<string, unknown>> = {
|
export type Widget<P extends Record<string, unknown>> = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -36,7 +35,7 @@ export const useWidgetPropsManager = <
|
||||||
configure: () => void;
|
configure: () => void;
|
||||||
} => {
|
} => {
|
||||||
const widgetProps = reactive(
|
const widgetProps = reactive(
|
||||||
props.widget ? deepClone(props.widget.data) : {}
|
props.widget ? structuredClone(toRaw(props.widget.data)) : {}
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergeProps = () => {
|
const mergeProps = () => {
|
||||||
|
@ -59,7 +58,7 @@ export const useWidgetPropsManager = <
|
||||||
});
|
});
|
||||||
|
|
||||||
const configure = async () => {
|
const configure = async () => {
|
||||||
const form = deepClone(propsDef);
|
const form = structuredClone(toRaw(propsDef));
|
||||||
for (const item of Object.keys(form)) {
|
for (const item of Object.keys(form)) {
|
||||||
form[item].default = widgetProps[item];
|
form[item].default = widgetProps[item];
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
"esnext",
|
"esnext",
|
||||||
"dom"
|
"dom"
|
||||||
],
|
],
|
||||||
"jsx": "preserve"
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "vue"
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"include": [
|
"include": [
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "magnetar-common",
|
"name": "magnetar-common",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"main": "index.js",
|
"main": "./built/index.js",
|
||||||
|
"types": "./built/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||||||
|
|
||||||
|
export interface BackendApiEndpoint<
|
||||||
|
M extends Method,
|
||||||
|
PP extends string[],
|
||||||
|
T,
|
||||||
|
R
|
||||||
|
> {
|
||||||
|
method: M;
|
||||||
|
endpoint: string;
|
||||||
|
pathParams: PP;
|
||||||
|
request?: T;
|
||||||
|
response?: R;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
|
||||||
|
switch (typeof data) {
|
||||||
|
case "string":
|
||||||
|
case "bigint":
|
||||||
|
case "boolean":
|
||||||
|
case "number":
|
||||||
|
case "symbol":
|
||||||
|
if (topLevel) return encodeURIComponent(data.toString()) + "=";
|
||||||
|
|
||||||
|
return data.toString();
|
||||||
|
case "object":
|
||||||
|
if (data === null) return "null";
|
||||||
|
|
||||||
|
if (Array.isArray(data))
|
||||||
|
return data
|
||||||
|
.map((d) => nestedUrlSearchParams(d, true))
|
||||||
|
.map(encodeURIComponent)
|
||||||
|
.join("&");
|
||||||
|
|
||||||
|
const inner = Object.entries(data).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
nestedUrlSearchParams(v, false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new URLSearchParams(inner).toString();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MagApiErrorCode = "Client:GenericApiError" | string;
|
||||||
|
|
||||||
|
export interface MagApiError {
|
||||||
|
status: number;
|
||||||
|
code: MagApiErrorCode;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MagApiClient {
|
||||||
|
private readonly baseUrl: string;
|
||||||
|
|
||||||
|
constructor(baseUrl: string) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async call<
|
||||||
|
T extends BackendApiEndpoint<
|
||||||
|
T["method"] & Method,
|
||||||
|
T["pathParams"] & string[],
|
||||||
|
T["request"],
|
||||||
|
T["response"]
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
{ endpoint, method }: T,
|
||||||
|
data: T["request"],
|
||||||
|
pathParams: {
|
||||||
|
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
|
||||||
|
| string
|
||||||
|
| number;
|
||||||
|
},
|
||||||
|
token?: string | null | undefined
|
||||||
|
): Promise<T["response"]> {
|
||||||
|
type Response = T["response"];
|
||||||
|
|
||||||
|
const authorizationToken = token ?? undefined;
|
||||||
|
const authorization = authorizationToken
|
||||||
|
? `Bearer ${authorizationToken}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
for (const name in pathParams) {
|
||||||
|
endpoint = endpoint.replace(`:${name}`, `${pathParams[name]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = this.baseUrl.replace(/\/+$/, "");
|
||||||
|
let url = `${baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
if (method === "GET") {
|
||||||
|
const query = nestedUrlSearchParams(data as any);
|
||||||
|
if (query) {
|
||||||
|
url += `?${query}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fetch(url, {
|
||||||
|
method,
|
||||||
|
body: method !== "GET" ? JSON.stringify(data) : undefined,
|
||||||
|
credentials: "omit",
|
||||||
|
cache: "no-cache",
|
||||||
|
headers: authorization ? { authorization } : {},
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
const body = res.status === 204 ? null : await res.json();
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
return body as Response;
|
||||||
|
} else if (res.status === 204) {
|
||||||
|
return null as any as Response;
|
||||||
|
} else {
|
||||||
|
throw body as MagApiError;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
throw {
|
||||||
|
status: -1,
|
||||||
|
code: "Client:GenericApiError",
|
||||||
|
message: e,
|
||||||
|
} as MagApiError;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { GetNoteById } from "./types/endpoints/GetNoteById";
|
||||||
|
export { GetTimeline } from "./types/endpoints/GetTimeline";
|
||||||
|
export { GetUserById } from "./types/endpoints/GetUserById";
|
||||||
|
export { GetUserByAcct } from "./types/endpoints/GetUserByAcct";
|
||||||
|
export { GetUserSelf } from "./types/endpoints/GetUserSelf";
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
BackendApiEndpoint,
|
||||||
|
MagApiClient,
|
||||||
|
MagApiError,
|
||||||
|
MagApiErrorCode,
|
||||||
|
Method,
|
||||||
|
} from "./be-api";
|
||||||
|
|
||||||
|
import {
|
||||||
|
feEndpoints,
|
||||||
|
FrontendApiEndpoint,
|
||||||
|
FrontendApiEndpoints,
|
||||||
|
} from "./fe-api";
|
||||||
|
|
||||||
|
export * as types from "./types";
|
||||||
|
export * as packed from "./packed";
|
||||||
|
export * as endpoints from "./endpoints";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Method,
|
||||||
|
BackendApiEndpoint,
|
||||||
|
MagApiError,
|
||||||
|
MagApiClient,
|
||||||
|
MagApiErrorCode,
|
||||||
|
feEndpoints,
|
||||||
|
FrontendApiEndpoint,
|
||||||
|
FrontendApiEndpoints,
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
export { PackDriveFileBase } from "./types/packed/PackDriveFileBase";
|
||||||
|
export { PackDriveFileFull } from "./types/packed/PackDriveFileFull";
|
||||||
|
export { PackDriveFileWithFolder } from "./types/packed/PackDriveFileWithFolder";
|
||||||
|
export { PackDriveFileWithUser } from "./types/packed/PackDriveFileWithUser";
|
||||||
|
export { PackDriveFolderBase } from "./types/packed/PackDriveFolderBase";
|
||||||
|
export { PackEmojiBase } from "./types/packed/PackEmojiBase";
|
||||||
|
export { PackDriveFolderWithParent } from "./types/packed/PackDriveFolderWithParent";
|
||||||
|
export { PackNoteBase } from "./types/packed/PackNoteBase";
|
||||||
|
export { PackNoteMaybeFull } from "./types/packed/PackNoteMaybeFull";
|
||||||
|
export { PackPollBase } from "./types/packed/PackPollBase";
|
||||||
|
export { PackNoteMaybeAttachments } from "./types/packed/PackNoteMaybeAttachments";
|
||||||
|
export { PackSecurityKeyBase } from "./types/packed/PackSecurityKeyBase";
|
||||||
|
export { PackUserBase } from "./types/packed/PackUserBase";
|
||||||
|
export { PackUserMaybeAll } from "./types/packed/PackUserMaybeAll";
|
||||||
|
export { PackUserSelf } from "./types/packed/PackUserSelf";
|
||||||
|
export { PackUserSelfMaybeAll } from "./types/packed/PackUserSelfMaybeAll";
|
|
@ -0,0 +1,40 @@
|
||||||
|
export { EmojiContext } from "./types/EmojiContext";
|
||||||
|
export { FollowVisibility } from "./types/FollowVisibility";
|
||||||
|
export { Id } from "./types/Id";
|
||||||
|
export { NotificationSettings } from "./types/NotificationSettings";
|
||||||
|
export { EmojiBase } from "./types/EmojiBase";
|
||||||
|
export { MmXml } from "./types/MmXml";
|
||||||
|
export { NotificationType } from "./types/NotificationType";
|
||||||
|
export { NoteBase } from "./types/NoteBase";
|
||||||
|
export { NoteAttachmentExt } from "./types/NoteAttachmentExt";
|
||||||
|
export { NoteDetailExt } from "./types/NoteDetailExt";
|
||||||
|
export { NoteListFilter } from "./types/NoteListFilter";
|
||||||
|
export { NoteSelfContextExt } from "./types/NoteSelfContextExt";
|
||||||
|
export { NoteVisibility } from "./types/NoteVisibility";
|
||||||
|
export { PollBase } from "./types/PollBase";
|
||||||
|
export { PollChoice } from "./types/PollChoice";
|
||||||
|
export { Reaction } from "./types/Reaction";
|
||||||
|
export { ReactionPair } from "./types/ReactionPair";
|
||||||
|
export { AvatarDecoration } from "./types/AvatarDecoration";
|
||||||
|
export { ProfileField } from "./types/ProfileField";
|
||||||
|
export { SecurityKeyBase } from "./types/SecurityKeyBase";
|
||||||
|
export { SpeechTransform } from "./types/SpeechTransform";
|
||||||
|
export { UserAuthOverviewExt } from "./types/UserAuthOverviewExt";
|
||||||
|
export { UserBase } from "./types/UserBase";
|
||||||
|
export { UserDetailExt } from "./types/UserDetailExt";
|
||||||
|
export { UserProfileExt } from "./types/UserProfileExt";
|
||||||
|
export { UserProfilePinsEx } from "./types/UserProfilePinsEx";
|
||||||
|
export { UserSecretsExt } from "./types/UserSecretsExt";
|
||||||
|
export { UserRelationExt } from "./types/UserRelationExt";
|
||||||
|
export { UserSelfExt } from "./types/UserSelfExt";
|
||||||
|
export { NoteByIdReq } from "./types/NoteByIdReq";
|
||||||
|
export { GetTimelineReq } from "./types/GetTimelineReq";
|
||||||
|
export { UserByIdReq } from "./types/UserByIdReq";
|
||||||
|
export { UserSelfReq } from "./types/UserSelfReq";
|
||||||
|
export { DriveFileBase } from "./types/DriveFileBase";
|
||||||
|
export { DriveFileFolderExt } from "./types/DriveFileFolderExt";
|
||||||
|
export { DriveFileUserExt } from "./types/DriveFileUserExt";
|
||||||
|
export { DriveFolderBase } from "./types/DriveFolderBase";
|
||||||
|
export { DriveFolderParentExt } from "./types/DriveFolderParentExt";
|
||||||
|
export { ImageMeta } from "./types/ImageMeta";
|
||||||
|
export { InstanceTicker } from "./types/InstanceTicker";
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type AvatarDecoration = "None" | "CatEars";
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { ImageMeta } from "./ImageMeta";
|
||||||
|
|
||||||
|
export interface DriveFileBase { name: string, created_at: string, size: number, hash: string | null, mime_type: string, media_metadata: ImageMeta, url: string | null, source_url: string, thumbnail_url: string | null, blurhash: string | null, sensitive: boolean, comment: string | null, folder_id: string | null, user_id: string | null, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackDriveFolderBase } from "./packed/PackDriveFolderBase";
|
||||||
|
|
||||||
|
export interface DriveFileFolderExt { folder: PackDriveFolderBase, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackUserBase } from "./packed/PackUserBase";
|
||||||
|
|
||||||
|
export interface DriveFileUserExt { user: PackUserBase, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface DriveFolderBase { name: string, created_at: string, comment: string | null, file_count: number, folder_count: number, parent_id: string | null, user_id: string, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { DriveFolderBase } from "./DriveFolderBase";
|
||||||
|
|
||||||
|
export interface DriveFolderParentExt { folder: DriveFolderBase, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface EmojiBase { shortcode: string, url: string, category: string | null, width: number | null, height: number | null, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackEmojiBase } from "./packed/PackEmojiBase";
|
||||||
|
|
||||||
|
export type EmojiContext = Array<PackEmojiBase>;
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type FollowVisibility = "Public" | "Followers" | "Private";
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { NoteListFilter } from "./NoteListFilter";
|
||||||
|
|
||||||
|
export interface GetTimelineReq { limit?: bigint, filter?: NoteListFilter, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface Id { id: string, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface ImageMeta { width: number | null, height: number | null, orientation: number | null, color: string | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface InstanceTicker { name: string | null, software_name: string | null, software_version: string | null, icon_url: string | null, favicon_url: string | null, theme_color: string | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type MmXml = string;
|
|
@ -0,0 +1,5 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackDriveFileBase } from "./packed/PackDriveFileBase";
|
||||||
|
import type { PackPollBase } from "./packed/PackPollBase";
|
||||||
|
|
||||||
|
export interface NoteAttachmentExt { poll: PackPollBase | null, attachments: Array<PackDriveFileBase>, }
|
|
@ -0,0 +1,8 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { EmojiContext } from "./EmojiContext";
|
||||||
|
import type { MmXml } from "./MmXml";
|
||||||
|
import type { NoteVisibility } from "./NoteVisibility";
|
||||||
|
import type { PackUserBase } from "./packed/PackUserBase";
|
||||||
|
import type { ReactionPair } from "./ReactionPair";
|
||||||
|
|
||||||
|
export interface NoteBase { created_at: string, updated_at: string | null, cw: string | null, cw_mm: MmXml | null, uri: string | null, url: string | null, text: string, text_mm: MmXml | null, visibility: NoteVisibility, user: PackUserBase, parent_note_id: string | null, renoted_note_id: string | null, reply_count: number, renote_count: number, mentions: Array<string>, visible_user_ids: Array<string> | null, hashtags: Array<string>, reactions: Array<ReactionPair>, local_only: boolean, has_poll: boolean, file_ids: Array<string>, emojis: EmojiContext, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface NoteByIdReq { context?: boolean, attachments?: boolean, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackNoteMaybeAttachments } from "./packed/PackNoteMaybeAttachments";
|
||||||
|
|
||||||
|
export interface NoteDetailExt { parent_note: PackNoteMaybeAttachments | null, renoted_note: PackNoteMaybeAttachments | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface NoteListFilter { show_renotes: boolean | null, show_replies: boolean | null, show_files_only: boolean | null, uncwed_sensitive: boolean | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface NoteSelfContextExt { self_renote_count: number | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type NoteVisibility = "Public" | "Home" | "Followers" | "Direct";
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { NotificationType } from "./NotificationType";
|
||||||
|
|
||||||
|
export interface NotificationSettings { enabled: Array<NotificationType>, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type NotificationType = "Follow" | "Mention" | "Reply" | "Renote" | "Quote" | "Reaction" | "PollVote" | "PollEnded" | "FollowRequest" | "FollowRequestAccepted" | "App";
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PollChoice } from "./PollChoice";
|
||||||
|
|
||||||
|
export interface PollBase { expires_at: string | null, expired: boolean, multiple_choice: boolean, options: Array<PollChoice>, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface PollChoice { title: string, votes_count: number, voted: boolean | null, }
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { MmXml } from "./MmXml";
|
||||||
|
|
||||||
|
export interface ProfileField { name: string, value: string, value_mm: MmXml | null, verified_at: string | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type Reaction = string | { name: string, host: string | null, url: string, } | { raw: string, };
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Reaction } from "./Reaction";
|
||||||
|
|
||||||
|
export type ReactionPair = [Reaction, number] | [Reaction, number, boolean];
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface SecurityKeyBase { name: string, last_used_at: string | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type SpeechTransform = "None" | "Cat";
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface UserAuthOverviewExt { has_two_factor_enabled: boolean, has_passwordless_login: boolean, has_security_keys: boolean, }
|
|
@ -0,0 +1,8 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AvatarDecoration } from "./AvatarDecoration";
|
||||||
|
import type { EmojiContext } from "./EmojiContext";
|
||||||
|
import type { InstanceTicker } from "./InstanceTicker";
|
||||||
|
import type { MmXml } from "./MmXml";
|
||||||
|
import type { SpeechTransform } from "./SpeechTransform";
|
||||||
|
|
||||||
|
export interface UserBase { acct: string, username: string, display_name: string, display_name_mm: MmXml | null, host: string | null, speech_transform: SpeechTransform, created_at: string, avatar_url: string, avatar_blurhash: string | null, avatar_decoration: AvatarDecoration, is_admin: boolean, is_moderator: boolean, is_bot: boolean, emojis: EmojiContext, instance: InstanceTicker | null, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface UserByIdReq { profile?: boolean, pins?: boolean, detail?: boolean, relation?: boolean, auth?: boolean, }
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface UserDetailExt { last_fetched_at: string | null, uri: string | null, updated_at: string | null, }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue