Fixed notification filtering and a bunch of bugs
ci/woodpecker/push/ociImagePush Pipeline is running
Details
ci/woodpecker/push/ociImagePush Pipeline is running
Details
This commit is contained in:
parent
7c2909bceb
commit
a169ebb004
|
@ -153,35 +153,3 @@ pub enum UserProfileFfvisibilityEnum {
|
|||
#[sea_orm(string_value = "public")]
|
||||
Public,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize)]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "user_profile_mutingnotificationtypes_enum"
|
||||
)]
|
||||
pub enum UserProfileMutingnotificationtypesEnum {
|
||||
#[sea_orm(string_value = "app")]
|
||||
App,
|
||||
#[sea_orm(string_value = "follow")]
|
||||
Follow,
|
||||
#[sea_orm(string_value = "followRequestAccepted")]
|
||||
FollowRequestAccepted,
|
||||
#[sea_orm(string_value = "groupInvited")]
|
||||
GroupInvited,
|
||||
#[sea_orm(string_value = "mention")]
|
||||
Mention,
|
||||
#[sea_orm(string_value = "pollEnded")]
|
||||
PollEnded,
|
||||
#[sea_orm(string_value = "pollVote")]
|
||||
PollVote,
|
||||
#[sea_orm(string_value = "quote")]
|
||||
Quote,
|
||||
#[sea_orm(string_value = "reaction")]
|
||||
Reaction,
|
||||
#[sea_orm(string_value = "receiveFollowRequest")]
|
||||
ReceiveFollowRequest,
|
||||
#[sea_orm(string_value = "renote")]
|
||||
Renote,
|
||||
#[sea_orm(string_value = "reply")]
|
||||
Reply,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
|
||||
|
||||
use super::sea_orm_active_enums::NotificationTypeEnum;
|
||||
use super::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
||||
use super::sea_orm_active_enums::UserProfileMutingnotificationtypesEnum;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -51,7 +51,7 @@ pub struct Model {
|
|||
#[sea_orm(column_name = "mutedWords", column_type = "JsonBinary")]
|
||||
pub muted_words: Json,
|
||||
#[sea_orm(column_name = "mutingNotificationTypes")]
|
||||
pub muting_notification_types: Vec<UserProfileMutingnotificationtypesEnum>,
|
||||
pub muting_notification_types: Vec<NotificationTypeEnum>,
|
||||
#[sea_orm(column_name = "noCrawle")]
|
||||
pub no_crawle: bool,
|
||||
#[sea_orm(column_name = "receiveAnnouncementEmail")]
|
||||
|
|
|
@ -10,6 +10,7 @@ mod m20240107_224446_generated_is_renote;
|
|||
mod m20240112_215106_remove_pages;
|
||||
mod m20240112_234759_remove_gallery;
|
||||
mod m20240115_212109_remove_poll_vote_notification;
|
||||
mod m20240228_155051_mag_notification_types_muting;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
@ -27,6 +28,7 @@ impl MigratorTrait for Migrator {
|
|||
Box::new(m20240112_215106_remove_pages::Migration),
|
||||
Box::new(m20240112_234759_remove_gallery::Migration),
|
||||
Box::new(m20240115_212109_remove_poll_vote_notification::Migration),
|
||||
Box::new(m20240228_155051_mag_notification_types_muting::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
use sea_orm_migration::sea_orm::TransactionTrait;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT;
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "notification_type_enum"[] USING '{}'::notification_type_enum[];
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'::notification_type_enum[];
|
||||
DROP TYPE user_profile_mutingnotificationtypes_enum;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
let txn = db.begin().await?;
|
||||
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE TYPE user_profile_mutingnotificationtypes_enum AS ENUM ('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app');
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT;
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "user_profile_mutingnotificationtypes_enum"[] USING '{}'::user_profile_mutingnotificationtypes_enum[];
|
||||
ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'::user_profile_mutingnotificationtypes_enum[];
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ impl FromQueryResult for NoteData {
|
|||
let prefix = if prefix.is_empty() {
|
||||
note::Entity.base_prefix()
|
||||
} else {
|
||||
MagIden::alias(&prefix)
|
||||
MagIden::alias(prefix)
|
||||
};
|
||||
|
||||
Ok(NoteData {
|
||||
|
|
|
@ -95,7 +95,7 @@ impl NotificationResolver {
|
|||
note_resolver: &NoteResolver,
|
||||
user_resolver: &UserResolver,
|
||||
) {
|
||||
q.add_aliased_columns::<notification::Entity>(¬ification_tbl);
|
||||
q.add_aliased_columns::<notification::Entity>(notification_tbl);
|
||||
|
||||
let notifier_tbl = notification_tbl.join_str(NOTIFIER);
|
||||
q.add_aliased_columns::<user::Entity>(¬ifier_tbl);
|
||||
|
@ -143,7 +143,7 @@ impl NotificationResolver {
|
|||
self.resolve(
|
||||
&mut query,
|
||||
¬ification_tbl,
|
||||
&resolve_options,
|
||||
resolve_options,
|
||||
&self.note_resolver,
|
||||
&self.user_resolver,
|
||||
);
|
||||
|
@ -183,7 +183,7 @@ impl NotificationResolver {
|
|||
self.resolve(
|
||||
&mut query,
|
||||
¬ification_tbl,
|
||||
&resolve_options,
|
||||
resolve_options,
|
||||
&self.note_resolver,
|
||||
&self.user_resolver,
|
||||
);
|
||||
|
|
|
@ -1,95 +1,96 @@
|
|||
{
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"watch": "pnpm vite build --watch --mode development",
|
||||
"watchRebuild": "pnpm vite build --watch",
|
||||
"build": "pnpm vite build",
|
||||
"format": "pnpm prettier --write '**/*.vue'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@phosphor-icons/web": "^2.0.3",
|
||||
"@rollup/plugin-alias": "3.1.9",
|
||||
"@rollup/plugin-json": "4.1.0",
|
||||
"@rollup/pluginutils": "^4.2.1",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/gulp": "4.0.11",
|
||||
"@types/gulp-rename": "2.0.2",
|
||||
"@types/katex": "0.16.0",
|
||||
"@types/matter-js": "0.18.2",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/seedrandom": "3.0.5",
|
||||
"@types/throttle-debounce": "5.0.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vue/compiler-sfc": "3.3.4",
|
||||
"@vue/runtime-core": "3.3.4",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "5.0.2",
|
||||
"blurhash": "1.1.5",
|
||||
"broadcast-channel": "4.19.1",
|
||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
|
||||
"calckey-js": "workspace:*",
|
||||
"chart.js": "4.3.0",
|
||||
"chartjs-adapter-date-fns": "3.0.0",
|
||||
"chartjs-chart-matrix": "^2.0.1",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"city-timezones": "^1.2.1",
|
||||
"compare-versions": "5.0.3",
|
||||
"cropperjs": "2.0.0-beta.2",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.11.0",
|
||||
"date-fns": "2.30.0",
|
||||
"emojilib": "github:thatonecalculator/emojilib",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eventemitter3": "4.0.7",
|
||||
"focus-trap": "^7.4.3",
|
||||
"focus-trap-vue": "^4.0.2",
|
||||
"gsap": "^3.11.5",
|
||||
"idb-keyval": "6.2.1",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"json5": "2.2.3",
|
||||
"katex": "0.16.7",
|
||||
"magnetar-common": "workspace:*",
|
||||
"matter-js": "0.18.0",
|
||||
"mfm-js": "0.23.3",
|
||||
"photoswipe": "5.3.7",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-vue": "1.1.6",
|
||||
"prismjs": "1.29.0",
|
||||
"punycode": "2.1.1",
|
||||
"querystring": "0.2.1",
|
||||
"rndstr": "1.0.0",
|
||||
"rollup": "3.23.1",
|
||||
"s-age": "1.1.2",
|
||||
"sass": "1.62.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "1.15.2",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"swiper": "9.3.2",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.146.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.5.2",
|
||||
"tsc-alias": "1.8.6",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typescript": "5.1.3",
|
||||
"unicode-emoji-json": "^0.4.0",
|
||||
"uuid": "9.0.0",
|
||||
"vanilla-tilt": "1.8.0",
|
||||
"vite": "4.3.9",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-isyourpasswordsafe": "^2.0.0",
|
||||
"vue-plyr": "^7.0.0",
|
||||
"vue3-otp-input": "^0.4.1",
|
||||
"vuedraggable": "4.1.0"
|
||||
}
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"watch": "pnpm vite build --watch --mode development",
|
||||
"watchRebuild": "pnpm vite build --watch",
|
||||
"build": "pnpm vite build",
|
||||
"format": "pnpm prettier --write '**/*.vue'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@phosphor-icons/web": "^2.0.3",
|
||||
"@rollup/plugin-alias": "3.1.9",
|
||||
"@rollup/plugin-json": "4.1.0",
|
||||
"@rollup/pluginutils": "^4.2.1",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/gulp": "4.0.11",
|
||||
"@types/gulp-rename": "2.0.2",
|
||||
"@types/katex": "0.16.0",
|
||||
"@types/matter-js": "0.18.2",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/seedrandom": "3.0.5",
|
||||
"@types/throttle-debounce": "5.0.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vue/compiler-sfc": "3.3.4",
|
||||
"@vue/runtime-core": "3.3.4",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "5.0.2",
|
||||
"blurhash": "1.1.5",
|
||||
"broadcast-channel": "4.19.1",
|
||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
|
||||
"calckey-js": "workspace:*",
|
||||
"chart.js": "4.3.0",
|
||||
"chartjs-adapter-date-fns": "3.0.0",
|
||||
"chartjs-chart-matrix": "^2.0.1",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"city-timezones": "^1.2.1",
|
||||
"compare-versions": "5.0.3",
|
||||
"cropperjs": "2.0.0-beta.2",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.11.0",
|
||||
"date-fns": "2.30.0",
|
||||
"emojilib": "github:thatonecalculator/emojilib",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eventemitter3": "4.0.7",
|
||||
"focus-trap": "^7.4.3",
|
||||
"focus-trap-vue": "^4.0.2",
|
||||
"gsap": "^3.11.5",
|
||||
"idb-keyval": "6.2.1",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"json5": "2.2.3",
|
||||
"katex": "0.16.7",
|
||||
"magnetar-common": "workspace:*",
|
||||
"matter-js": "0.18.0",
|
||||
"mfm-js": "0.23.3",
|
||||
"photoswipe": "5.3.7",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-vue": "1.1.6",
|
||||
"prismjs": "1.29.0",
|
||||
"punycode": "2.1.1",
|
||||
"querystring": "0.2.1",
|
||||
"rndstr": "1.0.0",
|
||||
"rollup": "3.23.1",
|
||||
"s-age": "1.1.2",
|
||||
"sass": "1.62.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "1.15.2",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"swiper": "9.3.2",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.146.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.5.2",
|
||||
"tsc-alias": "1.8.6",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typescript": "5.1.3",
|
||||
"unicode-emoji-json": "^0.4.0",
|
||||
"uuid": "9.0.0",
|
||||
"vanilla-tilt": "1.8.0",
|
||||
"vite": "4.3.9",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-component-type-helpers": "1.8.27",
|
||||
"vue-isyourpasswordsafe": "^2.0.0",
|
||||
"vue-plyr": "^7.0.0",
|
||||
"vue3-otp-input": "^0.4.1",
|
||||
"vuedraggable": "4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
notification.type === 'Mention' ||
|
||||
notification.type === 'Reply')
|
||||
"
|
||||
:key="notification.id"
|
||||
:key="'noteNotif' + notification.id"
|
||||
:note="notification.note"
|
||||
:collapsedReply="!!notification.note.parent_note"
|
||||
/>
|
||||
<XNotification
|
||||
v-else
|
||||
:key="notification.id"
|
||||
:key="'basicNotif' + notification.id"
|
||||
:notification="notification"
|
||||
:with-time="true"
|
||||
:full="true"
|
||||
|
@ -43,14 +43,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||
import type { ComponentExposed } from "vue-component-type-helpers";
|
||||
import XNotification from "@/components/MagNotification.vue";
|
||||
import XList from "@/components/MkDateSeparatedList.vue";
|
||||
import XNote from "@/components/MagNote.vue";
|
||||
import { magStream, stream } from "@/stream";
|
||||
import { $i } from "@/account";
|
||||
import MagPagination, { Paging } from "@/components/MagPagination.vue";
|
||||
import { endpoints, types } from "magnetar-common";
|
||||
import { endpoints, packed, types } from "magnetar-common";
|
||||
import { i18n } from "@/i18n";
|
||||
import { ChannelEvent } from "magnetar-common/built/types";
|
||||
|
||||
|
@ -59,10 +60,16 @@ const props = defineProps<{
|
|||
unreadOnly?: boolean;
|
||||
}>();
|
||||
|
||||
const pagingComponent = ref<InstanceType<typeof MagPagination>>();
|
||||
const pagingComponent =
|
||||
ref<
|
||||
ComponentExposed<
|
||||
typeof MagPagination<typeof endpoints.GetNotifications>
|
||||
>
|
||||
>();
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination: Paging<typeof endpoints.GetNotifications> = reactive({
|
||||
endpoint: endpoints.GetNotifications,
|
||||
pathParams: {},
|
||||
params: {
|
||||
include_types: props.includeTypes ?? undefined,
|
||||
exclude_types: props.includeTypes
|
||||
|
@ -70,9 +77,16 @@ const pagination: Paging = {
|
|||
: $i?.mutingNotificationTypes,
|
||||
unread_only: props.unreadOnly,
|
||||
} as types.NotificationsReq,
|
||||
};
|
||||
});
|
||||
|
||||
const onNotification = (notification) => {
|
||||
watch(
|
||||
() => props.includeTypes,
|
||||
(types) => {
|
||||
pagination.params!.include_types = types ?? undefined;
|
||||
}
|
||||
);
|
||||
|
||||
const onNotification = (notification: packed.PackNotification) => {
|
||||
const isMuted = props.includeTypes
|
||||
? !props.includeTypes.includes(notification.type)
|
||||
: $i?.mutingNotificationTypes?.includes(notification.type);
|
||||
|
@ -83,7 +97,7 @@ const onNotification = (notification) => {
|
|||
}
|
||||
|
||||
if (!isMuted) {
|
||||
pagingComponent.value.prepend({
|
||||
pagingComponent.value?.prepend({
|
||||
...notification,
|
||||
isRead: document.visibilityState === "visible",
|
||||
});
|
||||
|
|
|
@ -73,15 +73,7 @@
|
|||
T['paginated'] & true
|
||||
>"
|
||||
>
|
||||
import {
|
||||
computed,
|
||||
ComputedRef,
|
||||
isRef,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
|
||||
import * as os from "@/os";
|
||||
import {
|
||||
getScrollContainer,
|
||||
|
@ -99,16 +91,32 @@ import {
|
|||
import { SpanFilter } from "magnetar-common/built/types/SpanFilter";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
type PathParams = {
|
||||
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
|
||||
export type PathParams<
|
||||
P extends BackendApiEndpoint<
|
||||
P["method"] & Method,
|
||||
P["pathParams"] & string[],
|
||||
P["request"],
|
||||
P["response"],
|
||||
P["paginated"] & true
|
||||
>
|
||||
> = {
|
||||
[K in keyof P["pathParams"] as P["pathParams"][K] & string]:
|
||||
| string
|
||||
| number;
|
||||
};
|
||||
|
||||
export type Paging = {
|
||||
endpoint: T;
|
||||
pathParams: PathParams | ComputedRef<PathParams>;
|
||||
params?: T["request"] | ComputedRef<T["request"]>;
|
||||
export type Paging<
|
||||
P extends BackendApiEndpoint<
|
||||
P["method"] & Method,
|
||||
P["pathParams"] & string[],
|
||||
P["request"],
|
||||
P["response"],
|
||||
P["paginated"] & true
|
||||
>
|
||||
> = {
|
||||
endpoint: P;
|
||||
pathParams: PathParams<P>;
|
||||
params?: P["request"];
|
||||
|
||||
limit?: number;
|
||||
|
||||
|
@ -117,7 +125,7 @@ export type Paging = {
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
pagination: Paging;
|
||||
pagination: Paging<T>;
|
||||
disableAutoLoad?: boolean;
|
||||
displayLimit?: number;
|
||||
}>(),
|
||||
|
@ -146,12 +154,8 @@ const error = ref(false);
|
|||
const fetch = async (
|
||||
pagination: SpanFilter
|
||||
): Promise<PaginatedResult<T["response"]> & { data: { id: string } }[]> => {
|
||||
const pathParams = isRef(props.pagination.pathParams)
|
||||
? props.pagination.pathParams.value
|
||||
: props.pagination.pathParams;
|
||||
const params = isRef(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
: props.pagination.params;
|
||||
const pathParams = props.pagination.pathParams;
|
||||
const params = props.pagination.params;
|
||||
|
||||
return os
|
||||
.magApi(
|
||||
|
@ -196,6 +200,8 @@ const reload = (): void => {
|
|||
init();
|
||||
};
|
||||
|
||||
watch(props.pagination, reload);
|
||||
|
||||
const refresh = async (): Promise<void> => {
|
||||
fetch({}).then(
|
||||
(res) => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
:with-ok-button="true"
|
||||
:ok-button-disabled="false"
|
||||
@ok="ok()"
|
||||
@close="dialog.close()"
|
||||
@close="dialog?.close()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>{{ i18n.ts.notificationSetting }}</template>
|
||||
|
@ -31,7 +31,7 @@
|
|||
v-for="ntype in notificationTypes"
|
||||
:key="ntype"
|
||||
v-model="typesMap[ntype]"
|
||||
>{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch
|
||||
>{{ i18n.t(`_notification._magTypes.${ntype}`) }}</MkSwitch
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,25 +40,25 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import {} from "vue";
|
||||
import { notificationTypes } from "calckey-js";
|
||||
import MkSwitch from "./form/switch.vue";
|
||||
import MkInfo from "./MkInfo.vue";
|
||||
import MkButton from "./MkButton.vue";
|
||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import { notificationTypes, types } from "magnetar-common";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "done", v: { includingTypes: string[] | null }): void;
|
||||
(ev: "closed"): void;
|
||||
done: [{ includingTypes: types.NotificationType[] | null }];
|
||||
closed: [];
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
includingTypes?: (typeof notificationTypes)[number][] | null;
|
||||
includingTypes?: types.NotificationType[] | null;
|
||||
showGlobalToggle?: boolean;
|
||||
}>(),
|
||||
{
|
||||
includingTypes: () => [],
|
||||
includingTypes: () => notificationTypes,
|
||||
showGlobalToggle: true,
|
||||
}
|
||||
);
|
||||
|
@ -67,39 +67,35 @@ let includingTypes = $computed(() => props.includingTypes || []);
|
|||
|
||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||
|
||||
let typesMap = $ref<Record<(typeof notificationTypes)[number], boolean>>({});
|
||||
let useGlobalSetting = $ref(
|
||||
(includingTypes === null || includingTypes.length === 0) &&
|
||||
props.showGlobalToggle
|
||||
let typesMap = $ref(
|
||||
Object.fromEntries(
|
||||
notificationTypes.map((n) => [n, includingTypes.includes(n)])
|
||||
) as Record<types.NotificationType, boolean>
|
||||
);
|
||||
|
||||
for (const ntype of notificationTypes) {
|
||||
typesMap[ntype] = includingTypes.includes(ntype);
|
||||
}
|
||||
let useGlobalSetting = $ref(includingTypes === null || props.showGlobalToggle);
|
||||
|
||||
function ok() {
|
||||
if (useGlobalSetting) {
|
||||
emit("done", { includingTypes: null });
|
||||
} else {
|
||||
emit("done", {
|
||||
includingTypes: (
|
||||
Object.keys(typesMap) as (typeof notificationTypes)[number][]
|
||||
).filter((type) => typesMap[type]),
|
||||
includingTypes: notificationTypes.filter((type) => typesMap[type]),
|
||||
});
|
||||
}
|
||||
|
||||
dialog.close();
|
||||
dialog?.close();
|
||||
}
|
||||
|
||||
function disableAll() {
|
||||
for (const type in typesMap) {
|
||||
typesMap[type as (typeof notificationTypes)[number]] = false;
|
||||
typesMap[type as types.NotificationType] = false;
|
||||
}
|
||||
}
|
||||
|
||||
function enableAll() {
|
||||
for (const type in typesMap) {
|
||||
typesMap[type as (typeof notificationTypes)[number]] = true;
|
||||
typesMap[type as types.NotificationType] = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
types,
|
||||
} from "magnetar-common";
|
||||
import { magReactionToLegacy } from "@/scripts-mag/mag-util";
|
||||
import { ComponentProps } from "vue-component-type-helpers";
|
||||
|
||||
export const pendingApiRequestsCount = ref(0);
|
||||
|
||||
|
@ -307,11 +308,49 @@ export function getUniqueId(): string {
|
|||
return uniqueId++ + "";
|
||||
}
|
||||
|
||||
export async function popup(
|
||||
component: Component,
|
||||
props: Record<string, any>,
|
||||
events = {},
|
||||
disposeEvent?: string
|
||||
// See https://github.com/misskey-dev/misskey/blob/5f43c2faa2fae3866a9921d81ab43c3b9e8bd222/packages/frontend/src/os.ts#L150
|
||||
// We cannot use "vue-component-type-helpers"'s ComponentEmit, because it returns a type intersection, making it useless
|
||||
// for type checking of emit handlers
|
||||
//
|
||||
// We're not sure why the props were picked from T, because it didn't work, so we made it work on $props, which seems to work correctly
|
||||
type ComponentEmit<T> = T extends new () => { $props: infer Props }
|
||||
? [keyof Pick<Props, Extract<keyof Props, `on${string}`>>] extends [never]
|
||||
? Record<string, unknown>
|
||||
: EmitsExtractor<Props>
|
||||
: T extends (...args: any) => any
|
||||
? ReturnType<T> extends {
|
||||
[x: string]: any;
|
||||
__ctx?: { [x: string]: any; props: infer Props };
|
||||
}
|
||||
? [keyof Pick<Props, Extract<keyof Props, `on${string}`>>] extends [
|
||||
never
|
||||
]
|
||||
? Record<string, unknown>
|
||||
: EmitsExtractor<Props>
|
||||
: never
|
||||
: never;
|
||||
|
||||
type EmitsExtractor<T> = {
|
||||
[K in keyof T as K extends `onVnode${string}`
|
||||
? never
|
||||
: K extends `on${infer E}`
|
||||
? Uncapitalize<E>
|
||||
: K extends string
|
||||
? never
|
||||
: K]: T[K];
|
||||
};
|
||||
|
||||
type ComponentPropsRef<T extends Component> = {
|
||||
[K in keyof ComponentProps<T>]:
|
||||
| ComponentProps<T>[K]
|
||||
| Ref<ComponentProps<T>[K]>;
|
||||
};
|
||||
|
||||
export async function popup<C extends Component>(
|
||||
component: C,
|
||||
props: ComponentPropsRef<C>,
|
||||
events: Partial<ComponentEmit<C>> = {} as ComponentEmit<C>,
|
||||
disposeEvent?: keyof ComponentEmit<C>
|
||||
) {
|
||||
markRaw(component);
|
||||
|
||||
|
@ -1022,14 +1061,14 @@ export const deckGlobalEvents = new EventEmitter();
|
|||
|
||||
/*
|
||||
export function checkExistence(fileData: ArrayBuffer): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new FormData();
|
||||
data.append('md5', getMD5(fileData));
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new FormData();
|
||||
data.append('md5', getMD5(fileData));
|
||||
|
||||
os.api('drive/files/find-by-hash', {
|
||||
md5: getMD5(fileData)
|
||||
}).then(resp => {
|
||||
resolve(resp.length > 0 ? resp[0] : null);
|
||||
});
|
||||
});
|
||||
os.api('drive/files/find-by-hash', {
|
||||
md5: getMD5(fileData)
|
||||
}).then(resp => {
|
||||
resolve(resp.length > 0 ? resp[0] : null);
|
||||
});
|
||||
});
|
||||
}*/
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
import { computed, ref, watch } from "vue";
|
||||
import { Virtual } from "swiper";
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import { notificationTypes } from "calckey-js";
|
||||
import { notificationTypes, types } from "magnetar-common";
|
||||
import XNotifications from "@/components/MagNotifications.vue";
|
||||
import XNotes from "@/components/MkNotes.vue";
|
||||
import * as os from "@/os";
|
||||
|
@ -76,7 +76,7 @@ const tabs = ["all", "unread", "mentions", "directNotes"];
|
|||
let tab = $ref(tabs[0]);
|
||||
watch($$(tab), () => syncSlide(tabs.indexOf(tab)));
|
||||
|
||||
let includeTypes = $ref<string[] | null>(null);
|
||||
let includeTypes = $ref<types.NotificationType[]>();
|
||||
let unreadOnly = $computed(() => tab === "unread");
|
||||
os.api("notifications/mark-all-as-read");
|
||||
|
||||
|
@ -104,7 +104,7 @@ const directNotesPagination = {
|
|||
|
||||
function setFilter(ev) {
|
||||
const typeItems = notificationTypes.map((t) => ({
|
||||
text: i18n.t(`_notification._types.${t}`),
|
||||
text: i18n.t(`_notification._magTypes.${t}`),
|
||||
active: includeTypes && includeTypes.includes(t),
|
||||
action: () => {
|
||||
includeTypes = [t];
|
||||
|
@ -117,7 +117,7 @@ function setFilter(ev) {
|
|||
icon: "ph-x ph-bold ph-lg",
|
||||
text: i18n.ts.clear,
|
||||
action: () => {
|
||||
includeTypes = null;
|
||||
includeTypes = undefined;
|
||||
},
|
||||
},
|
||||
null,
|
||||
|
@ -191,6 +191,6 @@ function onSlideChange() {
|
|||
}
|
||||
|
||||
function syncSlide(index) {
|
||||
swiperRef.slideTo(index);
|
||||
swiperRef?.slideTo(index);
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from "vue";
|
||||
import { notificationTypes } from "calckey-js";
|
||||
import FormButton from "@/components/MkButton.vue";
|
||||
import FormSection from "@/components/form/section.vue";
|
||||
import * as os from "@/os";
|
||||
|
@ -55,6 +54,12 @@ import { $i } from "@/account";
|
|||
import { i18n } from "@/i18n";
|
||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||
import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue";
|
||||
import {
|
||||
magLegacyNotificationType,
|
||||
magNotificationType,
|
||||
} from "@/scripts-mag/mag-util";
|
||||
import { notificationTypes } from "magnetar-common";
|
||||
import type * as Misskey from "calckey-js";
|
||||
|
||||
let allowButton =
|
||||
$shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
|
||||
|
@ -75,7 +80,14 @@ async function readAllNotifications() {
|
|||
|
||||
function configure() {
|
||||
const includingTypes = notificationTypes.filter(
|
||||
(x) => !$i!.mutingNotificationTypes.includes(x)
|
||||
(x) =>
|
||||
!$i!.mutingNotificationTypes
|
||||
.map((s) =>
|
||||
magNotificationType(
|
||||
s as Misskey.entities.Notification["type"]
|
||||
)
|
||||
)
|
||||
.includes(x)
|
||||
);
|
||||
os.popup(
|
||||
defineAsyncComponent(
|
||||
|
@ -86,13 +98,14 @@ function configure() {
|
|||
showGlobalToggle: false,
|
||||
},
|
||||
{
|
||||
done: async (res) => {
|
||||
const { includingTypes: value } = res;
|
||||
done: async ({ includingTypes }) => {
|
||||
await os
|
||||
.apiWithDialog("i/update", {
|
||||
mutingNotificationTypes: notificationTypes.filter(
|
||||
(x) => !value.includes(x)
|
||||
),
|
||||
mutingNotificationTypes: notificationTypes
|
||||
.filter((x) => !includingTypes!.includes(x))
|
||||
.map(magLegacyNotificationType)
|
||||
.filter((s) => s)
|
||||
.map((s) => s!),
|
||||
})
|
||||
.then((i) => {
|
||||
$i!.mutingNotificationTypes = i.mutingNotificationTypes;
|
||||
|
|
|
@ -149,6 +149,64 @@ export function magEffectiveNote(
|
|||
return note.is_renote && note.renoted_note ? note.renoted_note : note;
|
||||
}
|
||||
|
||||
export function magLegacyNotificationType(
|
||||
nt: types.NotificationType | undefined
|
||||
): Misskey.entities.Notification["type"] | undefined {
|
||||
if (typeof nt === "undefined") return nt;
|
||||
|
||||
switch (nt) {
|
||||
case "Reply":
|
||||
return "reply";
|
||||
case "Renote":
|
||||
return "renote";
|
||||
case "Reaction":
|
||||
return "reaction";
|
||||
case "Quote":
|
||||
return "quote";
|
||||
case "Mention":
|
||||
return "mention";
|
||||
case "Follow":
|
||||
return "follow";
|
||||
case "FollowRequestAccepted":
|
||||
return "followRequestAccepted";
|
||||
case "FollowRequestReceived":
|
||||
return "receiveFollowRequest";
|
||||
case "App":
|
||||
return "app";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function magNotificationType(
|
||||
nt: Misskey.entities.Notification["type"] | undefined
|
||||
): types.NotificationType | undefined {
|
||||
if (typeof nt === "undefined") return nt;
|
||||
|
||||
switch (nt) {
|
||||
case "reply":
|
||||
return "Reply";
|
||||
case "renote":
|
||||
return "Renote";
|
||||
case "reaction":
|
||||
return "Reaction";
|
||||
case "quote":
|
||||
return "Quote";
|
||||
case "mention":
|
||||
return "Mention";
|
||||
case "follow":
|
||||
return "Follow";
|
||||
case "followRequestAccepted":
|
||||
return "FollowRequestAccepted";
|
||||
case "receiveFollowRequest":
|
||||
return "FollowRequestReceived";
|
||||
case "app":
|
||||
return "App";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function magLegacyVisibility(
|
||||
vis: types.NoteVisibility | Misskey.entities.Note["visibility"]
|
||||
): Misskey.entities.Note["visibility"];
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { throttle } from "throttle-debounce";
|
||||
import { markRaw } from "vue";
|
||||
import { notificationTypes } from "calckey-js";
|
||||
import { Storage } from "../../pizzax";
|
||||
import { api } from "@/os";
|
||||
import { types } from "magnetar-common";
|
||||
|
||||
type ColumnWidget = {
|
||||
name: string;
|
||||
|
@ -13,15 +13,15 @@ type ColumnWidget = {
|
|||
export type Column = {
|
||||
id: string;
|
||||
type:
|
||||
| "main"
|
||||
| "widgets"
|
||||
| "notifications"
|
||||
| "tl"
|
||||
| "antenna"
|
||||
| "list"
|
||||
| "mentions"
|
||||
| "direct"
|
||||
| "bookmarks";
|
||||
| "main"
|
||||
| "widgets"
|
||||
| "notifications"
|
||||
| "tl"
|
||||
| "antenna"
|
||||
| "list"
|
||||
| "mentions"
|
||||
| "direct"
|
||||
| "bookmarks";
|
||||
name: string | null;
|
||||
width: number;
|
||||
widgets?: ColumnWidget[];
|
||||
|
@ -29,7 +29,7 @@ export type Column = {
|
|||
flexible?: boolean;
|
||||
antennaId?: string;
|
||||
listId?: string;
|
||||
includingTypes?: (typeof notificationTypes)[number][];
|
||||
includingTypes?: types.NotificationType[];
|
||||
tl?: "home" | "local" | "social" | "global";
|
||||
};
|
||||
|
||||
|
@ -316,9 +316,9 @@ export function updateColumnWidget(
|
|||
column.widgets = column.widgets.map((w) =>
|
||||
w.id === widgetId
|
||||
? {
|
||||
...w,
|
||||
data: widgetData,
|
||||
}
|
||||
...w,
|
||||
data: widgetData,
|
||||
}
|
||||
: w
|
||||
);
|
||||
columns[columnIndex] = column;
|
||||
|
|
|
@ -10,18 +10,20 @@
|
|||
>{{ column.name }}</template
|
||||
>
|
||||
|
||||
<XNotifications :include-types="column.includingTypes" />
|
||||
<XNotifications :include-types="includingTypes" />
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from "vue";
|
||||
import { defineAsyncComponent, ref } from "vue";
|
||||
import XColumn from "./column.vue";
|
||||
import type { Column } from "./deck-store";
|
||||
import { updateColumn } from "./deck-store";
|
||||
import XNotifications from "@/components/MagNotifications.vue";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
import MkNotificationSettingWindow from "@/components/MkNotificationSettingWindow.vue";
|
||||
import { types } from "magnetar-common";
|
||||
|
||||
const props = defineProps<{
|
||||
column: Column;
|
||||
|
@ -32,8 +34,12 @@ const emit = defineEmits<{
|
|||
(ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void;
|
||||
}>();
|
||||
|
||||
const includingTypes = ref<types.NotificationType[] | undefined>(
|
||||
props.column.includingTypes
|
||||
);
|
||||
|
||||
function func(): void {
|
||||
os.popup(
|
||||
os.popup<typeof MkNotificationSettingWindow>(
|
||||
defineAsyncComponent(
|
||||
() => import("@/components/MkNotificationSettingWindow.vue")
|
||||
),
|
||||
|
@ -41,11 +47,11 @@ function func(): void {
|
|||
includingTypes: props.column.includingTypes,
|
||||
},
|
||||
{
|
||||
done: async (res) => {
|
||||
const { includingTypes } = res;
|
||||
done: async ({ includingTypes: notifTypes }) => {
|
||||
updateColumn(props.column.id, {
|
||||
includingTypes: includingTypes,
|
||||
includingTypes: notifTypes ?? undefined,
|
||||
});
|
||||
includingTypes.value = notifTypes ?? undefined;
|
||||
},
|
||||
},
|
||||
"closed"
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
<i class="ph-gear-six ph-bold ph-lg"></i></button
|
||||
></template>
|
||||
<div>
|
||||
<XNotifications :include-types="widgetProps.includingTypes" />
|
||||
<XNotifications
|
||||
:include-types="widgetProps.includingTypes as types.NotificationType[]"
|
||||
/>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
|
@ -30,8 +32,10 @@ import { useWidgetPropsManager, Widget, WidgetComponentExpose } from "./widget";
|
|||
import { GetFormResultType } from "@/scripts/form";
|
||||
import MkContainer from "@/components/MkContainer.vue";
|
||||
import XNotifications from "@/components/MagNotifications.vue";
|
||||
import MkNotificationSettingWindow from "@/components/MkNotificationSettingWindow.vue";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
import { types } from "magnetar-common";
|
||||
|
||||
const name = "notifications";
|
||||
|
||||
|
@ -46,8 +50,8 @@ const widgetPropsDef = {
|
|||
},
|
||||
includingTypes: {
|
||||
type: "array" as const,
|
||||
hidden: true,
|
||||
default: null,
|
||||
hidden: true as true,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -68,16 +72,16 @@ const { widgetProps, configure, save } = useWidgetPropsManager(
|
|||
|
||||
const configureNotification = () => {
|
||||
os.popup(
|
||||
defineAsyncComponent(
|
||||
defineAsyncComponent<typeof MkNotificationSettingWindow>(
|
||||
() => import("@/components/MkNotificationSettingWindow.vue")
|
||||
),
|
||||
{
|
||||
includingTypes: widgetProps.includingTypes,
|
||||
includingTypes:
|
||||
widgetProps.includingTypes as types.NotificationType[],
|
||||
},
|
||||
{
|
||||
done: async (res) => {
|
||||
const { includingTypes } = res;
|
||||
widgetProps.includingTypes = includingTypes;
|
||||
done: async ({ includingTypes }) => {
|
||||
widgetProps.includingTypes = includingTypes ?? undefined;
|
||||
save();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2122,7 +2122,18 @@ _notification:
|
|||
emptyPushNotificationMessage: "Push notifications have been updated"
|
||||
reacted: "reacted to your post"
|
||||
renoted: "boosted your post"
|
||||
voted: "voted on your poll"
|
||||
_magTypes:
|
||||
All: "All"
|
||||
Follow: "New followers"
|
||||
Mention: "Mentions"
|
||||
Reply: "Replies"
|
||||
Renote: "Boosts"
|
||||
Quote: "Quotes"
|
||||
Reaction: "Reactions"
|
||||
PollEnd: "Polls ending"
|
||||
FollowRequestReceived: "Received follow requests"
|
||||
FollowRequestAccepted: "Accepted follow requests"
|
||||
App: "Notifications from linked apps"
|
||||
_types:
|
||||
all: "All"
|
||||
follow: "New followers"
|
||||
|
|
|
@ -19,6 +19,19 @@ import * as types from "./types";
|
|||
import * as packed from "./packed";
|
||||
import * as endpoints from "./endpoints";
|
||||
|
||||
export const notificationTypes: types.NotificationType[] = [
|
||||
"Follow",
|
||||
"FollowRequestReceived",
|
||||
"FollowRequestAccepted",
|
||||
"Mention",
|
||||
"Reply",
|
||||
"Renote",
|
||||
"Reaction",
|
||||
"Quote",
|
||||
"PollEnd",
|
||||
"App",
|
||||
];
|
||||
|
||||
export {
|
||||
Method,
|
||||
BackendApiEndpoint,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { NotificationSettings } from "./NotificationSettings";
|
||||
|
||||
export interface UserSelfExt { avatar_id: string | null, banner_id: string | null, email_announcements: boolean, always_mark_sensitive: boolean, reject_bot_follow_requests: boolean, reject_crawlers: boolean, reject_ai_training: boolean, has_unread_announcements: boolean, has_unread_antenna: boolean, has_unread_notifications: boolean, has_pending_follow_requests: boolean, word_mutes: Array<string>, instance_mutes: Array<string>, notification_settings: NotificationSettings, }
|
||||
export interface UserSelfExt { avatar_id: string | null, banner_id: string | null, email_announcements: boolean, always_mark_sensitive: boolean, reject_bot_follow_requests: boolean, reject_crawlers: boolean, reject_ai_training: boolean, word_mutes: Array<string>, instance_mutes: Array<string>, notification_settings: NotificationSettings, }
|
|
@ -1,3 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface UserSelfReq { profile?: boolean, pins?: boolean, detail?: boolean, secrets?: boolean, }
|
||||
export interface UserSelfReq { profile?: boolean, pins?: boolean, detail?: boolean, secrets?: boolean, self_detail?: boolean, }
|
|
@ -3,7 +3,7 @@ import type { Empty } from "../Empty";
|
|||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||
|
||||
export const GetFollowRequestsSelf = {
|
||||
endpoint: "/users/@self/follow-requests",
|
||||
endpoint: "/users/@self/follow-requests" as "/users/@self/follow-requests",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as Empty,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Empty } from "../Empty";
|
|||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||
|
||||
export const GetFollowersById = {
|
||||
endpoint: "/users/:id/followers",
|
||||
endpoint: "/users/:id/followers" as "/users/:id/followers",
|
||||
pathParams: ["id"] as ["id"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as Empty,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Empty } from "../Empty";
|
|||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||
|
||||
export const GetFollowersSelf = {
|
||||
endpoint: "/users/@self/followers",
|
||||
endpoint: "/users/@self/followers" as "/users/@self/followers",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as Empty,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Empty } from "../Empty";
|
|||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||
|
||||
export const GetFollowingById = {
|
||||
endpoint: "/users/:id/following",
|
||||
endpoint: "/users/:id/following" as "/users/:id/following",
|
||||
pathParams: ["id"] as ["id"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as Empty,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Empty } from "../Empty";
|
|||
import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||
|
||||
export const GetFollowingSelf = {
|
||||
endpoint: "/users/@self/following",
|
||||
endpoint: "/users/@self/following" as "/users/@self/following",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as Empty,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { ManyUsersByIdReq } from "../ManyUsersByIdReq";
|
|||
import type { PackUserBase } from "../packed/PackUserBase";
|
||||
|
||||
export const GetManyUsersById = {
|
||||
endpoint: "/users/lookup-many",
|
||||
endpoint: "/users/lookup-many" as "/users/lookup-many",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as ManyUsersByIdReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { NoteByIdReq } from "../NoteByIdReq";
|
|||
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
||||
|
||||
export const GetNoteById = {
|
||||
endpoint: "/notes/:id",
|
||||
endpoint: "/notes/:id" as "/notes/:id",
|
||||
pathParams: ["id"] as ["id"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as NoteByIdReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { NotificationsReq } from "../NotificationsReq";
|
|||
import type { PackNotification } from "../PackNotification";
|
||||
|
||||
export const GetNotifications = {
|
||||
endpoint: "/users/@self/notifications",
|
||||
endpoint: "/users/@self/notifications" as "/users/@self/notifications",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as NotificationsReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { GetTimelineReq } from "../GetTimelineReq";
|
|||
import type { PackNoteMaybeFull } from "../packed/PackNoteMaybeFull";
|
||||
|
||||
export const GetTimeline = {
|
||||
endpoint: "/timeline/:timeline_type",
|
||||
endpoint: "/timeline/:timeline_type" as "/timeline/:timeline_type",
|
||||
pathParams: ["timeline_type"] as ["timeline_type"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as GetTimelineReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
|||
import type { UserByIdReq } from "../UserByIdReq";
|
||||
|
||||
export const GetUserByAcct = {
|
||||
endpoint: "/users/by-acct/:user_acct",
|
||||
endpoint: "/users/by-acct/:user_acct" as "/users/by-acct/:user_acct",
|
||||
pathParams: ["user_acct"] as ["user_acct"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as UserByIdReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
|||
import type { UserByIdReq } from "../UserByIdReq";
|
||||
|
||||
export const GetUserById = {
|
||||
endpoint: "/users/:user_id",
|
||||
endpoint: "/users/:user_id" as "/users/:user_id",
|
||||
pathParams: ["user_id"] as ["user_id"],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as UserByIdReq,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { PackUserSelfMaybeAll } from "../packed/PackUserSelfMaybeAll";
|
|||
import type { UserSelfReq } from "../UserSelfReq";
|
||||
|
||||
export const GetUserSelf = {
|
||||
endpoint: "/users/@self",
|
||||
endpoint: "/users/@self" as "/users/@self",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as UserSelfReq,
|
||||
|
|
|
@ -5,5 +5,6 @@ import type { UserDetailExt } from "../UserDetailExt";
|
|||
import type { UserProfileExt } from "../UserProfileExt";
|
||||
import type { UserProfilePinsEx } from "../UserProfilePinsEx";
|
||||
import type { UserSecretsExt } from "../UserSecretsExt";
|
||||
import type { UserSelfExt } from "../UserSelfExt";
|
||||
|
||||
export type PackUserSelfMaybeAll = Id & UserBase & Partial<UserProfileExt> & Partial<UserProfilePinsEx> & Partial<UserDetailExt> & Partial<UserSecretsExt>;
|
||||
export type PackUserSelfMaybeAll = Id & UserBase & Partial<UserProfileExt> & Partial<UserProfilePinsEx> & Partial<UserDetailExt> & Partial<UserSecretsExt> & Partial<UserSelfExt>;
|
|
@ -344,6 +344,9 @@ importers:
|
|||
vue:
|
||||
specifier: 3.3.4
|
||||
version: 3.3.4
|
||||
vue-component-type-helpers:
|
||||
specifier: 1.8.27
|
||||
version: 1.8.27
|
||||
vue-isyourpasswordsafe:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
|
@ -1016,7 +1019,7 @@ packages:
|
|||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': ^1.2.66
|
||||
chokidar: ^3.5.1
|
||||
chokidar: ^3.3.1
|
||||
peerDependenciesMeta:
|
||||
chokidar:
|
||||
optional: true
|
||||
|
@ -7436,6 +7439,10 @@ packages:
|
|||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vue-component-type-helpers@1.8.27:
|
||||
resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==}
|
||||
dev: true
|
||||
|
||||
/vue-isyourpasswordsafe@2.0.0:
|
||||
resolution: {integrity: sha512-j3ORj18R9AgFiP2UOM35KuZbSeJAUiwCSyeRBFN3CGFYTJSKsxqU9qGqOHOz6OhLAYKMTin8JOmqugAbF9O+Bg==}
|
||||
dependencies:
|
||||
|
|
|
@ -59,7 +59,7 @@ fn split_tag_inner(tag: impl AsRef<str>) -> (String, Option<String>) {
|
|||
let tag = tag.strip_prefix('@').unwrap_or(tag.as_ref());
|
||||
|
||||
match tag.split_once('@') {
|
||||
Some((name, host)) if name.is_empty() => (host.to_owned(), None),
|
||||
Some((name, "")) => (name.to_owned(), None),
|
||||
Some((name, host)) => (name.to_owned(), Some(host.to_owned())),
|
||||
None => (tag.to_owned(), None),
|
||||
}
|
||||
|
|
|
@ -406,7 +406,7 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
|||
fn decl() -> String {
|
||||
format!(
|
||||
"const {} = {{\n \
|
||||
endpoint: \"{}\",\n \
|
||||
endpoint: \"{}\" as \"{}\",\n \
|
||||
pathParams: {} as {},\n \
|
||||
method: \"{}\" as \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\",\n \
|
||||
request: undefined as unknown as {},\n \
|
||||
|
@ -416,6 +416,7 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
|||
",
|
||||
Self::name(),
|
||||
Self::ENDPOINT,
|
||||
Self::ENDPOINT,
|
||||
#path_params,
|
||||
#path_params,
|
||||
Self::METHOD,
|
||||
|
|
|
@ -20,6 +20,8 @@ pub struct UserSelfReq {
|
|||
pub detail: Option<bool>,
|
||||
#[ts(optional)]
|
||||
pub secrets: Option<bool>,
|
||||
#[ts(optional)]
|
||||
pub self_detail: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Endpoint)]
|
||||
|
|
|
@ -138,10 +138,6 @@ pub struct UserSelfExt {
|
|||
pub reject_bot_follow_requests: bool,
|
||||
pub reject_crawlers: bool,
|
||||
pub reject_ai_training: bool,
|
||||
pub has_unread_announcements: bool,
|
||||
pub has_unread_antenna: bool,
|
||||
pub has_unread_notifications: bool,
|
||||
pub has_pending_follow_requests: bool,
|
||||
pub word_mutes: Vec<String>,
|
||||
pub instance_mutes: Vec<String>,
|
||||
pub notification_settings: NotificationSettings,
|
||||
|
@ -210,6 +206,7 @@ pack!(
|
|||
& Optional<UserProfilePinsEx> as pins
|
||||
& Optional<UserDetailExt> as detail
|
||||
& Optional<UserSecretsExt> as secrets
|
||||
& Optional<UserSelfExt> as self_detail
|
||||
);
|
||||
|
||||
impl From<PackUserBase> for PackUserSelfMaybeAll {
|
||||
|
@ -221,6 +218,7 @@ impl From<PackUserBase> for PackUserSelfMaybeAll {
|
|||
Optional(None),
|
||||
Optional(None),
|
||||
Optional(None),
|
||||
Optional(None),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ use magnetar_sdk::types::drive::PackDriveFileBase;
|
|||
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
||||
use magnetar_sdk::types::instance::InstanceTicker;
|
||||
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
||||
use magnetar_sdk::types::notification::NotificationType;
|
||||
use magnetar_sdk::types::user::{
|
||||
AvatarDecoration, MovedTo, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
||||
UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx,
|
||||
UserRelationExt, UserSecretsExt,
|
||||
UserRelationExt, UserSecretsExt, UserSelfExt,
|
||||
};
|
||||
use magnetar_sdk::types::MmXml;
|
||||
use magnetar_sdk::types::{MmXml, NotificationSettings};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::model::{PackType, PackingContext};
|
||||
|
@ -68,7 +70,7 @@ impl PackType<UserBaseSource<'_>> for UserBase {
|
|||
is_moderator: user.is_moderator,
|
||||
is_bot: user.is_bot,
|
||||
emojis: emoji_context.clone(),
|
||||
instance: instance.map(|i| i.clone()),
|
||||
instance: instance.cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,10 +113,7 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
|||
description: profile.description.clone(),
|
||||
description_mm: description_mm.cloned(),
|
||||
location: profile.location.clone(),
|
||||
birthday: profile
|
||||
.birthday
|
||||
.clone()
|
||||
.and_then(|b| b.parse().map_or_else(|_| None, Some)),
|
||||
birthday: profile.birthday.clone().and_then(|b| b.parse().ok()),
|
||||
fields: profile_fields.clone(),
|
||||
follower_count: follow_visibility.then_some(user.followers_count as usize),
|
||||
following_count: follow_visibility.then_some(user.following_count as usize),
|
||||
|
@ -215,3 +214,29 @@ impl PackType<&ck::user_profile::Model> for UserAuthOverviewExt {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PackType<(&ck::user::Model, &ck::user_profile::Model)> for UserSelfExt {
|
||||
fn extract(
|
||||
context: &PackingContext,
|
||||
(user, profile): (&ck::user::Model, &ck::user_profile::Model),
|
||||
) -> Self {
|
||||
UserSelfExt {
|
||||
avatar_id: user.avatar_id.clone(),
|
||||
banner_id: user.banner_id.clone(),
|
||||
email_announcements: profile.receive_announcement_email,
|
||||
always_mark_sensitive: profile.always_mark_nsfw,
|
||||
reject_bot_follow_requests: profile.careful_bot,
|
||||
reject_crawlers: profile.no_crawle,
|
||||
reject_ai_training: profile.prevent_ai_learning,
|
||||
word_mutes: Vec::<String>::deserialize(&profile.muted_words).unwrap_or_else(|_| vec![]),
|
||||
instance_mutes: vec![],
|
||||
notification_settings: NotificationSettings {
|
||||
enabled: profile
|
||||
.muting_notification_types
|
||||
.iter()
|
||||
.map(|n| NotificationType::extract(context, n))
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,6 +137,6 @@ impl PackingContext {
|
|||
write.insert(link, relationship);
|
||||
drop(write);
|
||||
|
||||
return Ok(relationship);
|
||||
Ok(relationship)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ impl DriveModel {
|
|||
Required(DriveFileBase::extract(
|
||||
ctx,
|
||||
PackFileBaseInput {
|
||||
file: &file,
|
||||
file,
|
||||
effective_url: url.as_ref(),
|
||||
effective_thumbnail_url: thumbnail_url.as_ref(),
|
||||
},
|
||||
|
|
|
@ -59,7 +59,7 @@ impl EmojiModel {
|
|||
pub fn pack_existing(&self, ctx: &PackingContext, emoji: &ck::emoji::Model) -> PackEmojiBase {
|
||||
PackEmojiBase::pack_from((
|
||||
Required(Id::from(&emoji.id)),
|
||||
Required(EmojiBase::extract(ctx, &emoji)),
|
||||
Required(EmojiBase::extract(ctx, emoji)),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ impl EmojiModel {
|
|||
host: Option<&str>,
|
||||
) -> PackResult<Vec<PackEmojiBase>> {
|
||||
let emojis = ctx.service.emoji_cache.get_many(shortcodes, host).await?;
|
||||
let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, &e)).collect();
|
||||
let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, e)).collect();
|
||||
|
||||
Ok(packed_emojis)
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ impl EmojiModel {
|
|||
tags: &[EmojiTag<'_>],
|
||||
) -> PackResult<Vec<PackEmojiBase>> {
|
||||
let emojis = ctx.service.emoji_cache.get_many_tagged(tags).await?;
|
||||
let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, &e)).collect();
|
||||
let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, e)).collect();
|
||||
|
||||
Ok(packed_emojis)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use magnetar_sdk::types::instance::InstanceTicker;
|
|||
use magnetar_sdk::types::user::{
|
||||
MovedTo, PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll,
|
||||
ProfileField, SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
||||
UserProfilePinsEx, UserRelationExt, UserRelationship, UserSecretsExt,
|
||||
UserProfilePinsEx, UserRelationExt, UserRelationship, UserSecretsExt, UserSelfExt,
|
||||
};
|
||||
use magnetar_sdk::types::{Id, MmXml};
|
||||
use magnetar_sdk::{mmm, Optional, Packed, Required};
|
||||
|
@ -166,7 +166,7 @@ impl UserModel {
|
|||
let base = UserBase::extract(
|
||||
ctx,
|
||||
UserBaseSource {
|
||||
user: &user,
|
||||
user,
|
||||
username_mm: mmm::to_xml_string(&username_mm).map(MmXml).as_ref().ok(),
|
||||
avatar_url,
|
||||
avatar: avatar.as_ref(),
|
||||
|
@ -243,14 +243,14 @@ impl UserModel {
|
|||
None => None,
|
||||
};
|
||||
|
||||
let description_mm = self.tokenize_description(&profile);
|
||||
let description_mm = self.tokenize_description(profile);
|
||||
|
||||
let fields = Vec::<ProfileFieldRaw>::deserialize(&profile.fields)?;
|
||||
let parser = mmm::Context::new(2);
|
||||
let profile_fields = fields
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
let tok = parser.parse_profile_fields(&f.value);
|
||||
let tok = parser.parse_profile_fields(f.value);
|
||||
|
||||
ProfileField {
|
||||
name: f.name.to_string(),
|
||||
|
@ -263,7 +263,7 @@ impl UserModel {
|
|||
|
||||
if let Some(desc_mm) = &description_mm {
|
||||
let emoji_model = EmojiModel;
|
||||
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(&desc_mm));
|
||||
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(desc_mm));
|
||||
let emojis = emoji_model
|
||||
.fetch_many_emojis(ctx, &shortcodes, user.host.as_deref())
|
||||
.await?;
|
||||
|
@ -275,7 +275,7 @@ impl UserModel {
|
|||
ctx,
|
||||
UserProfileExtSource {
|
||||
user,
|
||||
profile: &profile,
|
||||
profile,
|
||||
profile_fields: &profile_fields,
|
||||
banner: banner.as_ref(),
|
||||
description_mm: description_mm
|
||||
|
@ -322,7 +322,9 @@ impl UserModel {
|
|||
let user = user_data.user();
|
||||
|
||||
let should_fetch_profile = user_data.profile().is_none()
|
||||
&& (req.profile.unwrap_or_default() || req.secrets.unwrap_or_default());
|
||||
&& (req.profile.unwrap_or_default()
|
||||
|| req.secrets.unwrap_or_default()
|
||||
|| req.self_detail.unwrap_or_default());
|
||||
let profile_raw_promise =
|
||||
OptionFuture::from(should_fetch_profile.then(|| self.get_profile(ctx, user)));
|
||||
let (base_res, profile_res) =
|
||||
|
@ -370,6 +372,11 @@ impl UserModel {
|
|||
.map(|notes| UserProfilePinsEx::extract(ctx, ¬es));
|
||||
let secrets_resolved = secrets_res.transpose()?;
|
||||
|
||||
let self_info = req
|
||||
.self_detail
|
||||
.unwrap_or_default()
|
||||
.then(|| UserSelfExt::extract(ctx, (&user, profile_ref.unwrap())));
|
||||
|
||||
Ok(PackUserSelfMaybeAll {
|
||||
id: base.id,
|
||||
user: base.user,
|
||||
|
@ -377,6 +384,7 @@ impl UserModel {
|
|||
pins: Optional(pins_resolved),
|
||||
detail: Optional(detail),
|
||||
secrets: Optional(secrets_resolved),
|
||||
self_detail: Optional(self_info),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn serialize_as_urlenc(value: &Value) -> String {
|
|||
.map(|(k, v)| {
|
||||
format!(
|
||||
"{}={}",
|
||||
utf8_percent_encode(&k, NON_ALPHANUMERIC).to_string(),
|
||||
utf8_percent_encode(k, NON_ALPHANUMERIC),
|
||||
if matches!(v, Value::Array(_) | Value::Object(_)) {
|
||||
utf8_percent_encode(&serialize_as_urlenc(v), NON_ALPHANUMERIC)
|
||||
.to_string()
|
||||
|
|
|
@ -114,7 +114,7 @@ impl IntoResponseParts for Pagination {
|
|||
let wrap = |uri: Uri, query: String, rel: Either<RelPrev, RelNext>| {
|
||||
format!(
|
||||
"<{}?{}>; rel=\"{}\"",
|
||||
uri.to_string(),
|
||||
uri,
|
||||
query,
|
||||
rel.as_ref()
|
||||
.map_either(RelPrev::as_ref, RelNext::as_ref)
|
||||
|
|
Loading…
Reference in New Issue