[Astal] Notifications: Delete not working version
This commit is contained in:
@@ -2,7 +2,7 @@ import { bind } from "astal";
|
|||||||
import { Gtk } from "astal/gtk4";
|
import { Gtk } from "astal/gtk4";
|
||||||
import Notifd from "gi://AstalNotifd";
|
import Notifd from "gi://AstalNotifd";
|
||||||
import { NotificationIcon } from "./Icon";
|
import { NotificationIcon } from "./Icon";
|
||||||
import { createTimeoutManager } from "../../../util/notifd";
|
import { createTimeoutManager, time, urgency } from "../../../util/notifd";
|
||||||
|
|
||||||
export function NotificationWidget({
|
export function NotificationWidget({
|
||||||
notification,
|
notification,
|
||||||
@@ -81,8 +81,8 @@ export function NotificationWidget({
|
|||||||
halign={CENTER}
|
halign={CENTER}
|
||||||
valign={CENTER}
|
valign={CENTER}
|
||||||
vexpand={true}
|
vexpand={true}
|
||||||
|
child={NotificationIcon(notification)!}
|
||||||
>
|
>
|
||||||
{NotificationIcon(notification)}
|
|
||||||
</box>
|
</box>
|
||||||
<box
|
<box
|
||||||
vertical
|
vertical
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AstalNotifd from "gi://AstalNotifd"
|
||||||
|
|
||||||
|
const notifd = AstalNotifd.get_default()
|
||||||
|
const getNotifications = () => {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/*
|
|
||||||
* dotfiles - handler.ts
|
|
||||||
*
|
|
||||||
* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
|
|
||||||
* https://janishutz.com, development@janishutz.com
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { App, Astal, Gdk } from "astal/gtk4"
|
|
||||||
import Notifd from "gi://AstalNotifd";
|
|
||||||
import Notification from "./notifications";
|
|
||||||
import { timeout, Variable } from "astal"
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
// Config
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
const TIMEOUT_DELAY = 5000;
|
|
||||||
let isRunning = false;
|
|
||||||
let notificationMenuOpen = false;
|
|
||||||
|
|
||||||
interface NotificationDetails {
|
|
||||||
notification: Notifd.Notification;
|
|
||||||
backendID: number;
|
|
||||||
notifdID: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
// ╭───────────────────────────────────────────────╮
|
|
||||||
// │ Handler │
|
|
||||||
// ╰───────────────────────────────────────────────╯
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
let ShownNotifications: Variable<number[]> = Variable( [] );
|
|
||||||
let Notifications: NotificationDetails[] = [];
|
|
||||||
|
|
||||||
const notifd = Notifd.get_default();
|
|
||||||
notifd.ignoreTimeout = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a notification by its internal ID
|
|
||||||
* @param index The notifd ID of the notification
|
|
||||||
*/
|
|
||||||
const deleteNotification = ( index: number ): void => {
|
|
||||||
hideNotification( index );
|
|
||||||
Notifications.splice( index, 1 );
|
|
||||||
if ( Notifications.length === 0 ) {
|
|
||||||
notificationMenuOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a notification by notifd id
|
|
||||||
* @param id The notifd ID of the notification
|
|
||||||
*/
|
|
||||||
const deleteNotificationByNotificationID = ( id: number ): void => {
|
|
||||||
const index = findNotificationByNotificationID( id );
|
|
||||||
if ( index > -1 ) {
|
|
||||||
deleteNotification( index );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the internal ID from the notifd id for a notification (helper function)
|
|
||||||
* @param id The notifd ID of the notification
|
|
||||||
* @returns The internal ID or -1 if not found
|
|
||||||
*/
|
|
||||||
const findNotificationByNotificationID = ( id: number ): number => {
|
|
||||||
// Find index in Notifications array
|
|
||||||
for (let index = 0; index < Notifications.length; index++) {
|
|
||||||
if ( Notifications[ index ].notifdID === id ) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a notification to the notification handler
|
|
||||||
* @param id The notifd ID of the notification
|
|
||||||
*/
|
|
||||||
const addNotification = ( id: number ): void => {
|
|
||||||
const currIndex = Notifications.length;
|
|
||||||
Notifications.push( {
|
|
||||||
notifdID: id,
|
|
||||||
backendID: currIndex,
|
|
||||||
notification: notifd.get_notification( id )
|
|
||||||
} );
|
|
||||||
|
|
||||||
showNotification( currIndex );
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the notifd runner and handle notifications.
|
|
||||||
*/
|
|
||||||
const hookToNotificationDaemon = (): void => {
|
|
||||||
if ( isRunning ) {
|
|
||||||
printerr( '[ Notifications ] Error: Already running' );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isRunning = true;
|
|
||||||
|
|
||||||
notifd.connect( 'notified', ( _, id ) => {
|
|
||||||
addNotification( id );
|
|
||||||
} );
|
|
||||||
|
|
||||||
notifd.connect( 'resolved', ( _, id ) => {
|
|
||||||
deleteNotificationByNotificationID( id );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a notification. It will stay on screen (regardless of removeAgain passed in), if
|
|
||||||
* critical urgency
|
|
||||||
* @param id The internal id (index in Notifications array)
|
|
||||||
* @param removeAgain = true If to remove the notification from the screen again automatically
|
|
||||||
*/
|
|
||||||
const showNotification = ( id: number, removeAgain: boolean = true ) => {
|
|
||||||
// Add notification to UI for display
|
|
||||||
const not = [...ShownNotifications.get()].reverse();
|
|
||||||
not.push( id );
|
|
||||||
ShownNotifications.set( not.reverse() );
|
|
||||||
|
|
||||||
// Set delay to remove the notification again
|
|
||||||
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
|
|
||||||
timeout( TIMEOUT_DELAY, () => {
|
|
||||||
hideNotification( id );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop displaying notification
|
|
||||||
* @param id The internal id (index in the Notifications array)
|
|
||||||
*/
|
|
||||||
const hideNotification = ( id: number ) => {
|
|
||||||
if ( !notificationMenuOpen ) {
|
|
||||||
const not = [...ShownNotifications.get()];
|
|
||||||
not.splice( not.indexOf( id ), 1 );
|
|
||||||
ShownNotifications.set( not );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the notification menu. Called by toggleNotificationMenu too
|
|
||||||
*/
|
|
||||||
const openNotificationMenu = () => {
|
|
||||||
// Simply show all notifications
|
|
||||||
notificationMenuOpen = true;
|
|
||||||
const not = [];
|
|
||||||
for (let index = 0; index < Notifications.length; index++) {
|
|
||||||
not.push( index );
|
|
||||||
}
|
|
||||||
ShownNotifications.set( not.reverse() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the notification menu. Called by toggleNotificationMenu too
|
|
||||||
*/
|
|
||||||
const closeNotificationMenu = () => {
|
|
||||||
// Hide all notifications
|
|
||||||
notificationMenuOpen = true;
|
|
||||||
ShownNotifications.set( [] );
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the notification menu (i.e. show all notifications)
|
|
||||||
*/
|
|
||||||
const toggleNotificationMenu = (): string => {
|
|
||||||
if ( notificationMenuOpen ) {
|
|
||||||
closeNotificationMenu();
|
|
||||||
return 'Toggle notification menu closed';
|
|
||||||
} else {
|
|
||||||
openNotificationMenu();
|
|
||||||
return 'Toggled notification menu open';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all notifications
|
|
||||||
*/
|
|
||||||
const clearAllNotifications = () => {
|
|
||||||
Notifications = [];
|
|
||||||
ShownNotifications.set( [] );
|
|
||||||
// TODO: Hiding for each individual deleteNotification
|
|
||||||
notificationMenuOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the newest notifications
|
|
||||||
*/
|
|
||||||
const clearNewestNotifications = () => {
|
|
||||||
const not = [...ShownNotifications.get()];
|
|
||||||
not.splice( 0, 1 );
|
|
||||||
ShownNotifications.set( not );
|
|
||||||
|
|
||||||
Notifications.splice( Notifications.length - 1, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
// ╭───────────────────────────────────────────────╮
|
|
||||||
// │ User Interface │
|
|
||||||
// ╰───────────────────────────────────────────────╯
|
|
||||||
// ───────────────────────────────────────────────────────────────────
|
|
||||||
const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
|
|
||||||
const { TOP, RIGHT } = Astal.WindowAnchor
|
|
||||||
|
|
||||||
hookToNotificationDaemon();
|
|
||||||
|
|
||||||
return <window
|
|
||||||
cssClasses={["NotificationHandler"]}
|
|
||||||
gdkmonitor={gdkmonitor}
|
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
|
||||||
anchor={TOP | RIGHT}
|
|
||||||
visible={ShownNotifications( list => list.length > 0 )}
|
|
||||||
application={App}>
|
|
||||||
<box vertical>
|
|
||||||
{ShownNotifications( list => list.map( i => {
|
|
||||||
// i is index in ShownNotifications array
|
|
||||||
return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification>
|
|
||||||
} ) ) }
|
|
||||||
</box>
|
|
||||||
</window>
|
|
||||||
}
|
|
||||||
|
|
||||||
const cliHandler = ( args: string[] ): string => {
|
|
||||||
if ( args[ 1 ] == 'show' ) {
|
|
||||||
openNotificationMenu();
|
|
||||||
return 'Showing all open notifications';
|
|
||||||
} else if ( args[ 1 ] == 'hide' ) {
|
|
||||||
closeNotificationMenu();
|
|
||||||
return 'Hid all notifications';
|
|
||||||
} else if ( args[ 1 ] == 'clear' ) {
|
|
||||||
clearAllNotifications();
|
|
||||||
return 'Cleared all notifications';
|
|
||||||
} else if ( args[ 1 ] == 'clear-newest' ) {
|
|
||||||
clearNewestNotifications();
|
|
||||||
return 'Cleared newest notification';
|
|
||||||
} else if ( args[ 1 ] == 'toggle' ) {
|
|
||||||
return toggleNotificationMenu();
|
|
||||||
} else if ( args[ 1 ] == 'list' ){
|
|
||||||
if ( Notifications.length > 0 ) {
|
|
||||||
let list = 'Currently unviewed notifications: ';
|
|
||||||
for (let index = 0; index < Notifications.length; index++) {
|
|
||||||
const element = Notifications[index];
|
|
||||||
|
|
||||||
list += `\n - (${element.notifdID}) ${element.notification.get_app_name()}: ${element.notification.get_summary()}`;
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
} else {
|
|
||||||
return 'No currently unviewed notifications'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 'Unknown command!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
startNotificationHandler,
|
|
||||||
cliHandler
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Gtk, Gdk } from "astal/gtk4";
|
|
||||||
import { GLib } from "astal";
|
|
||||||
|
|
||||||
export const isIcon = (icon: string) => {
|
|
||||||
const display = Gdk.Display.get_default();
|
|
||||||
if (!display) return false;
|
|
||||||
const iconTheme = Gtk.IconTheme.get_for_display(display);
|
|
||||||
return iconTheme.has_icon(icon);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fileExists = (path: string) =>
|
|
||||||
GLib.file_test(path, GLib.FileTest.EXISTS);
|
|
||||||
@@ -8,14 +8,16 @@ export function NotificationIcon(notification: Notifd.Notification) {
|
|||||||
const icon = notification.image || notification.appIcon || notification.desktopEntry;
|
const icon = notification.image || notification.appIcon || notification.desktopEntry;
|
||||||
if (fileExists(icon)) {
|
if (fileExists(icon)) {
|
||||||
return (
|
return (
|
||||||
<box expand={false} valign={Gtk.Align.CENTER}>
|
<box hexpand={false} valign={Gtk.Align.CENTER} child={
|
||||||
<image file={icon} />
|
<image file={icon} />
|
||||||
|
}>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
} else if (isIcon(icon)) {
|
} else if (isIcon(icon)) {
|
||||||
return (
|
return (
|
||||||
<box expand={false} valign={Gtk.Align.CENTER}>
|
<box hexpand={false} valign={Gtk.Align.CENTER} child={
|
||||||
<image iconName={icon} />
|
<image iconName={icon} />
|
||||||
|
}>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Astal } from "astal/gtk4";
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import Hyprland from "gi://AstalHyprland";
|
||||||
|
import { bind } from "astal";
|
||||||
|
import { NotificationWidget } from "./modules/Notification";
|
||||||
|
import { hyprToGdk } from "../../util/hyprland";
|
||||||
|
|
||||||
|
export default function Notifications() {
|
||||||
|
const notifd = Notifd.get_default();
|
||||||
|
const hyprland = Hyprland.get_default();
|
||||||
|
const { TOP, RIGHT } = Astal.WindowAnchor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name="notifications"
|
||||||
|
gdkmonitor={bind(hyprland, "focusedMonitor").as(
|
||||||
|
(focused: Hyprland.Monitor) => hyprToGdk(focused),
|
||||||
|
)}
|
||||||
|
anchor={TOP | RIGHT}
|
||||||
|
visible={bind(notifd, "notifications").as(
|
||||||
|
(notifications) => notifications.length > 0,
|
||||||
|
)}
|
||||||
|
child={
|
||||||
|
<box vertical={true} cssClasses={["notifications"]}>
|
||||||
|
{bind(notifd, "notifications").as((notifications) =>
|
||||||
|
notifications.map((n) => <NotificationWidget notification={n} />),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// From astal examples
|
|
||||||
|
|
||||||
import { bind, GLib } from "astal";
|
|
||||||
import { Gtk } from "astal/gtk4";
|
|
||||||
import Notifd from "gi://AstalNotifd";
|
|
||||||
import { NotificationIcon } from "./icon";
|
|
||||||
// import Pango from "gi://Pango?version=1.0"
|
|
||||||
|
|
||||||
const fileExists = (path: string) => GLib.file_test(path, GLib.FileTest.EXISTS);
|
|
||||||
|
|
||||||
const time = (time: number, format = "%H:%M") =>
|
|
||||||
GLib.DateTime.new_from_unix_local(time).format(format)!;
|
|
||||||
|
|
||||||
const urgency = (n: Notifd.Notification) => {
|
|
||||||
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency;
|
|
||||||
// match operator when?
|
|
||||||
switch (n.urgency) {
|
|
||||||
case LOW:
|
|
||||||
return "low";
|
|
||||||
case CRITICAL:
|
|
||||||
return "critical";
|
|
||||||
case NORMAL:
|
|
||||||
default:
|
|
||||||
return "normal";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
delete: (id: number) => void;
|
|
||||||
notification: Notifd.Notification;
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
|
||||||
const { notification: n, id: id, delete: del } = props;
|
|
||||||
const { START, CENTER, END } = Gtk.Align;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box vertical cssClasses={["notification", `${urgency(n)}`]}>
|
|
||||||
<box cssClasses={["header"]}>
|
|
||||||
{n.appIcon || n.desktopEntry ? (
|
|
||||||
<Gtk.Image
|
|
||||||
cssClasses={["app-icon"]}
|
|
||||||
visible={Boolean(n.appIcon || n.desktopEntry)}
|
|
||||||
iconName={n.appIcon || n.desktopEntry}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<image iconName={"window-close-symbolic"}></image>
|
|
||||||
)}
|
|
||||||
<label
|
|
||||||
cssClasses={["app-name"]}
|
|
||||||
halign={START}
|
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
|
||||||
label={n.appName || "Unknown"}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
cssClasses={["time"]}
|
|
||||||
hexpand
|
|
||||||
halign={END}
|
|
||||||
label={time(n.time)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClicked={() => {
|
|
||||||
del(id);
|
|
||||||
}}
|
|
||||||
child={<image iconName="window-close-symbolic" />}
|
|
||||||
></button>
|
|
||||||
</box>
|
|
||||||
<Gtk.Separator visible />
|
|
||||||
<box cssClasses={["content"]}>
|
|
||||||
<box
|
|
||||||
cssClasses={["image"]}
|
|
||||||
visible={Boolean(NotificationIcon(n))}
|
|
||||||
halign={CENTER}
|
|
||||||
valign={CENTER}
|
|
||||||
vexpand={true}
|
|
||||||
>
|
|
||||||
{NotificationIcon(n)}
|
|
||||||
</box>
|
|
||||||
<box vertical>
|
|
||||||
<label
|
|
||||||
cssClasses={["summary"]}
|
|
||||||
halign={START}
|
|
||||||
xalign={0}
|
|
||||||
useMarkup
|
|
||||||
label={bind(n, "summary")}
|
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
|
||||||
/>
|
|
||||||
{n.body && (
|
|
||||||
<label
|
|
||||||
cssClasses={["body"]}
|
|
||||||
valign={CENTER}
|
|
||||||
wrap={true}
|
|
||||||
maxWidthChars={50}
|
|
||||||
label={bind(n, "body")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
{n.get_actions().length > 0 ? (
|
|
||||||
<box cssClasses={["actions"]}>
|
|
||||||
{n.get_actions().map(({ label, id }) => (
|
|
||||||
<button hexpand onClicked={() => n.invoke(id)}>
|
|
||||||
<label label={label} halign={CENTER} hexpand />
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</box>
|
|
||||||
) : (
|
|
||||||
<box></box>
|
|
||||||
)}
|
|
||||||
</box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,744 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import stylistic from '@stylistic/eslint-plugin';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import typescript from '@typescript-eslint/eslint-plugin';
|
||||||
|
import vue from 'eslint-plugin-vue';
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
'plugins': {
|
||||||
|
'@stylistic': stylistic,
|
||||||
|
'@stylistic/js': stylistic,
|
||||||
|
'@stylistic/ts': stylistic
|
||||||
|
},
|
||||||
|
'files': [
|
||||||
|
'**/*.ts',
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.mjs',
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/*.tsx',
|
||||||
|
'**/*.jsx'
|
||||||
|
],
|
||||||
|
'rules': {
|
||||||
|
'sort-imports': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
'ignoreCase': false,
|
||||||
|
'ignoreDeclarationSort': false,
|
||||||
|
'ignoreMemberSort': false,
|
||||||
|
'memberSyntaxSortOrder': [
|
||||||
|
'none',
|
||||||
|
'all',
|
||||||
|
'multiple',
|
||||||
|
'single'
|
||||||
|
],
|
||||||
|
'allowSeparatedGroups': false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Formatting
|
||||||
|
'@stylistic/array-bracket-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'multiline': false,
|
||||||
|
'minItems': 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/array-bracket-spacing': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/array-element-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'consistent': false,
|
||||||
|
'multiline': false,
|
||||||
|
'minItems': 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/arrow-parens': [
|
||||||
|
'error',
|
||||||
|
'as-needed'
|
||||||
|
],
|
||||||
|
'@stylistic/arrow-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/block-spacing': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/brace-style': [
|
||||||
|
'error',
|
||||||
|
'1tbs',
|
||||||
|
{
|
||||||
|
'allowSingleLine': false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/comma-dangle': [
|
||||||
|
'error',
|
||||||
|
'never'
|
||||||
|
],
|
||||||
|
'@stylistic/comma-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/comma-style': [
|
||||||
|
'error',
|
||||||
|
'last'
|
||||||
|
],
|
||||||
|
'@stylistic/dot-location': [
|
||||||
|
'error',
|
||||||
|
'property'
|
||||||
|
],
|
||||||
|
'@stylistic/function-call-argument-newline': [
|
||||||
|
'error',
|
||||||
|
'consistent'
|
||||||
|
],
|
||||||
|
'@stylistic/function-call-spacing': [
|
||||||
|
'error',
|
||||||
|
'never'
|
||||||
|
],
|
||||||
|
'@stylistic/function-paren-newline': [
|
||||||
|
'error',
|
||||||
|
'multiline-arguments'
|
||||||
|
],
|
||||||
|
'@stylistic/implicit-arrow-linebreak': [
|
||||||
|
'error',
|
||||||
|
'beside'
|
||||||
|
],
|
||||||
|
'@stylistic/indent': [
|
||||||
|
'error',
|
||||||
|
4
|
||||||
|
],
|
||||||
|
'@stylistic/indent-binary-ops': [
|
||||||
|
'error',
|
||||||
|
4
|
||||||
|
],
|
||||||
|
'@stylistic/key-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'beforeColon': false,
|
||||||
|
'afterColon': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/keyword-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/lines-between-class-members': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/max-len': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
'code': 140,
|
||||||
|
'comments': 160,
|
||||||
|
'ignoreComments': false,
|
||||||
|
'ignoreUrls': true,
|
||||||
|
'ignoreStrings': true,
|
||||||
|
'ignoreTemplateLiterals': true,
|
||||||
|
'ignoreRegExpLiterals': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/max-statements-per-line': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/multiline-ternary': [
|
||||||
|
'error',
|
||||||
|
'always-multiline'
|
||||||
|
],
|
||||||
|
'@stylistic/new-parens': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/newline-per-chained-call': 'error',
|
||||||
|
'@stylistic/no-confusing-arrow': 'error',
|
||||||
|
'@stylistic/no-extra-parens': [
|
||||||
|
'error',
|
||||||
|
'all',
|
||||||
|
{
|
||||||
|
'nestedBinaryExpressions': false,
|
||||||
|
'ternaryOperandBinaryExpressions': false,
|
||||||
|
'ignoreJSX': 'multi-line',
|
||||||
|
'nestedConditionalExpressions': false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/no-extra-semi': 'error',
|
||||||
|
'@stylistic/no-floating-decimal': 'error',
|
||||||
|
'@stylistic/no-mixed-operators': 'error',
|
||||||
|
'@stylistic/no-mixed-spaces-and-tabs': 'error',
|
||||||
|
'@stylistic/no-multi-spaces': 'error',
|
||||||
|
'@stylistic/no-multiple-empty-lines': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'max': 3,
|
||||||
|
'maxEOF': 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/no-tabs': 'error',
|
||||||
|
'@stylistic/no-trailing-spaces': 'error',
|
||||||
|
'@stylistic/no-whitespace-before-property': 'error',
|
||||||
|
'@stylistic/object-curly-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'multiline': true,
|
||||||
|
'minProperties': 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/object-curly-spacing': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/object-property-newline': 'error',
|
||||||
|
'@stylistic/one-var-declaration-per-line': 'error',
|
||||||
|
'@stylistic/operator-linebreak': [
|
||||||
|
'error',
|
||||||
|
'before'
|
||||||
|
],
|
||||||
|
'@stylistic/padded-blocks': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'blocks': 'never',
|
||||||
|
'classes': 'always',
|
||||||
|
'switches': 'never'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Padding lines. The most in-depth part of this config
|
||||||
|
'@stylistic/padding-line-between-statements': [
|
||||||
|
'error',
|
||||||
|
// Variables, Constants
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'var',
|
||||||
|
'next': 'var'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'let',
|
||||||
|
'next': 'let'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'const',
|
||||||
|
'next': 'const'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'var',
|
||||||
|
'next': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'cjs-import',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'import',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'with'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'let',
|
||||||
|
'next': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'cjs-import',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'import',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'const',
|
||||||
|
'next': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'cjs-import',
|
||||||
|
'class',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'import',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// Import
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'import',
|
||||||
|
'next': 'import'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'cjs-import',
|
||||||
|
'next': 'cjs-import'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
],
|
||||||
|
'next': 'cjs-import'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'cjs-import',
|
||||||
|
'next': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
],
|
||||||
|
'next': 'import'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'import',
|
||||||
|
'next': [
|
||||||
|
'block',
|
||||||
|
'block-like',
|
||||||
|
'break',
|
||||||
|
'cjs-export',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'directive',
|
||||||
|
'do',
|
||||||
|
'empty',
|
||||||
|
'export',
|
||||||
|
'expression',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'iife',
|
||||||
|
'let',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'throw',
|
||||||
|
'try',
|
||||||
|
'var',
|
||||||
|
'while',
|
||||||
|
'with'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// If
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'if'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'if',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// For
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'for'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'for',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// While
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'while'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'while',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Functions
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'function',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Block Statements
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'block-like'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'block-like',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Switch
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'switch',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Try-Catch
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'try'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'try',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Throw
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'throw'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'throw',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Return
|
||||||
|
{
|
||||||
|
'blankLine': 'never',
|
||||||
|
'prev': 'return',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'return'
|
||||||
|
},
|
||||||
|
// Export
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'export'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'export',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'cjs-export'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'cjs-export',
|
||||||
|
'next': '*'
|
||||||
|
},
|
||||||
|
// Classes
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': '*',
|
||||||
|
'next': 'class'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'blankLine': 'always',
|
||||||
|
'prev': 'class',
|
||||||
|
'next': '*'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/quote-props': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/quotes': [
|
||||||
|
'error',
|
||||||
|
'single'
|
||||||
|
],
|
||||||
|
'@stylistic/rest-spread-spacing': [
|
||||||
|
'error',
|
||||||
|
'never'
|
||||||
|
],
|
||||||
|
'@stylistic/semi': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/semi-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/semi-style': [
|
||||||
|
'error',
|
||||||
|
'last'
|
||||||
|
],
|
||||||
|
'@stylistic/space-before-blocks': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/space-before-function-paren': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/space-in-parens': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/space-infix-ops': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'int32Hint': false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/space-unary-ops': 'error',
|
||||||
|
'@stylistic/spaced-comment': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/switch-colon-spacing': 'error',
|
||||||
|
'@stylistic/template-curly-spacing': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/template-tag-spacing': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'@stylistic/type-generic-spacing': 'error',
|
||||||
|
'@stylistic/type-named-tuple-spacing': 'error',
|
||||||
|
'@stylistic/wrap-iife': [
|
||||||
|
'error',
|
||||||
|
'inside'
|
||||||
|
],
|
||||||
|
'@stylistic/wrap-regex': 'error',
|
||||||
|
'@stylistic/ts/type-annotation-spacing': 'error'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
export default tseslint.config(
|
||||||
|
// Base JavaScript rules
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
style,
|
||||||
|
|
||||||
|
// Vue support (including TS and JSX inside SFCs)
|
||||||
|
{
|
||||||
|
'files': [ '**/*.vue' ],
|
||||||
|
'languageOptions': {
|
||||||
|
'sourceType': 'module',
|
||||||
|
'ecmaVersion': 'latest',
|
||||||
|
'globals': globals.browser,
|
||||||
|
'parserOptions': {
|
||||||
|
'parser': tseslint.parser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'plugins': {
|
||||||
|
'vue': vue,
|
||||||
|
'@stylistic': stylistic,
|
||||||
|
'@stylistic/js': stylistic,
|
||||||
|
'@stylistic/ts': stylistic,
|
||||||
|
'@typescript-eslint': typescript
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...vue.configs['flat/recommended']
|
||||||
|
],
|
||||||
|
'rules': {
|
||||||
|
...typescript.configs.recommended.rules,
|
||||||
|
...style.rules,
|
||||||
|
|
||||||
|
// Vue specific rules
|
||||||
|
'@stylistic/indent': 'off',
|
||||||
|
'vue/html-indent': [
|
||||||
|
'error',
|
||||||
|
4
|
||||||
|
],
|
||||||
|
'vue/html-comment-indent': [
|
||||||
|
'error',
|
||||||
|
4
|
||||||
|
],
|
||||||
|
'vue/script-indent': [
|
||||||
|
'error',
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
'baseIndent': 1,
|
||||||
|
'switchCase': 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'vue/html-self-closing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'html': {
|
||||||
|
'void': 'never',
|
||||||
|
'normal': 'never',
|
||||||
|
'component': 'always'
|
||||||
|
},
|
||||||
|
'svg': 'always',
|
||||||
|
'math': 'never'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'vue/max-attributes-per-line': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'singleline': 3,
|
||||||
|
'multiline': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user