Fixed notification filtering and a bunch of bugs
ci/woodpecker/push/ociImagePush Pipeline is running Details

This commit is contained in:
Natty 2024-02-29 00:26:09 +01:00
parent 7c2909bceb
commit a169ebb004
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
46 changed files with 495 additions and 279 deletions

View File

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

View File

@ -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")]

View File

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

View File

@ -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(())
}
}

View File

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

View File

@ -95,7 +95,7 @@ impl NotificationResolver {
note_resolver: &NoteResolver,
user_resolver: &UserResolver,
) {
q.add_aliased_columns::<notification::Entity>(&notification_tbl);
q.add_aliased_columns::<notification::Entity>(notification_tbl);
let notifier_tbl = notification_tbl.join_str(NOTIFIER);
q.add_aliased_columns::<user::Entity>(&notifier_tbl);
@ -143,7 +143,7 @@ impl NotificationResolver {
self.resolve(
&mut query,
&notification_tbl,
&resolve_options,
resolve_options,
&self.note_resolver,
&self.user_resolver,
);
@ -183,7 +183,7 @@ impl NotificationResolver {
self.resolve(
&mut query,
&notification_tbl,
&resolve_options,
resolve_options,
&self.note_resolver,
&self.user_resolver,
);

View File

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

View File

@ -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",
});

View File

@ -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) => {

View File

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

View File

@ -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);
});
});
}*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
},
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
import type { UserByIdReq } from "../UserByIdReq";
export const GetUserById = {
endpoint: "/users/:user_id",