v3: linter and formatter pass
This commit is contained in:
@@ -107,7 +107,9 @@ export async function createUrlDump(
|
||||
broadcastNewDump(dump);
|
||||
notifyUserFollowersNewDump(userId, dumpId, title);
|
||||
}
|
||||
if (request.comment) notifyMentions(userId, request.comment, "dump", dumpId, title);
|
||||
if (request.comment) {
|
||||
notifyMentions(userId, request.comment, "dump", dumpId, title);
|
||||
}
|
||||
return dump;
|
||||
}
|
||||
|
||||
@@ -335,7 +337,13 @@ export async function updateDump(
|
||||
if (updatedDump.isPrivate && !dump.isPrivate) broadcastDumpDeleted(dumpId);
|
||||
else if (!updatedDump.isPrivate) broadcastDumpUpdated(updatedDump);
|
||||
if (updatedDump.comment) {
|
||||
notifyMentions(dump.userId, updatedDump.comment, "dump", dumpId, updatedDump.title);
|
||||
notifyMentions(
|
||||
dump.userId,
|
||||
updatedDump.comment,
|
||||
"dump",
|
||||
dumpId,
|
||||
updatedDump.title,
|
||||
);
|
||||
}
|
||||
return updatedDump;
|
||||
}
|
||||
|
||||
@@ -207,9 +207,11 @@ export function notifyMentions(
|
||||
).get(mentionerUserId) as { username: string } | undefined;
|
||||
if (!mentionerRow) return;
|
||||
|
||||
const usernames = [...new Set(
|
||||
const usernames = [
|
||||
...new Set(
|
||||
[...body.matchAll(MENTION_RE)].map((m) => m[1].toLowerCase()),
|
||||
)];
|
||||
),
|
||||
];
|
||||
|
||||
for (const username of usernames) {
|
||||
const mentionedRow = db.prepare(
|
||||
|
||||
@@ -73,7 +73,9 @@ export function createPlaylist(
|
||||
isPublic: req.isPublic,
|
||||
createdAt,
|
||||
};
|
||||
if (req.description) notifyMentions(userId, req.description, "playlist", id, req.title);
|
||||
if (req.description) {
|
||||
notifyMentions(userId, req.description, "playlist", id, req.title);
|
||||
}
|
||||
broadcastPlaylistCreated(playlist);
|
||||
return playlist;
|
||||
}
|
||||
@@ -157,7 +159,14 @@ export function updatePlaylist(
|
||||
const newSlug = makeSlug(newTitle, playlist.id);
|
||||
db.prepare(
|
||||
`UPDATE playlists SET title = ?, slug = ?, description = ?, is_public = ?, updated_at = ? WHERE id = ?;`,
|
||||
).run(newTitle, newSlug, newDescription, newIsPublic ? 1 : 0, now.toISOString(), playlist.id);
|
||||
).run(
|
||||
newTitle,
|
||||
newSlug,
|
||||
newDescription,
|
||||
newIsPublic ? 1 : 0,
|
||||
now.toISOString(),
|
||||
playlist.id,
|
||||
);
|
||||
|
||||
const updated: Playlist = {
|
||||
...playlist,
|
||||
@@ -167,7 +176,15 @@ export function updatePlaylist(
|
||||
isPublic: newIsPublic,
|
||||
updatedAt: now,
|
||||
};
|
||||
if (newDescription) notifyMentions(requestingUserId, newDescription, "playlist", playlist.id, newTitle);
|
||||
if (newDescription) {
|
||||
notifyMentions(
|
||||
requestingUserId,
|
||||
newDescription,
|
||||
"playlist",
|
||||
playlist.id,
|
||||
newTitle,
|
||||
);
|
||||
}
|
||||
broadcastPlaylistUpdated(updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -2666,8 +2666,12 @@ body.has-player .fab-new {
|
||||
}
|
||||
|
||||
@keyframes comment-highlight {
|
||||
0% { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
|
||||
100% { background: transparent; }
|
||||
0% {
|
||||
background: color-mix(in srgb, var(--color-accent) 18%, transparent);
|
||||
}
|
||||
100% {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.comment-node--highlight {
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -161,7 +161,9 @@ function CommentNode({
|
||||
{children.length > 0 && (
|
||||
<ul
|
||||
className="comment-replies"
|
||||
style={depth >= MAX_INDENT_DEPTH ? { paddingLeft: 0, marginLeft: 0, borderLeft: "none" } : undefined}
|
||||
style={depth >= MAX_INDENT_DEPTH
|
||||
? { paddingLeft: 0, marginLeft: 0, borderLeft: "none" }
|
||||
: undefined}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<CommentNode
|
||||
@@ -229,7 +231,9 @@ function CommentNode({
|
||||
value={editBody}
|
||||
onChange={setEditBody}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleEditSave(e);
|
||||
if (
|
||||
e.key === "Enter" && (e.ctrlKey || e.metaKey)
|
||||
) handleEditSave(e);
|
||||
}}
|
||||
autoResize
|
||||
rows={1}
|
||||
@@ -299,7 +303,10 @@ function CommentNode({
|
||||
<ConfirmModal
|
||||
message="Delete this comment?"
|
||||
confirmLabel="Delete"
|
||||
onConfirm={() => { setConfirmDelete(false); handleDelete(); }}
|
||||
onConfirm={() => {
|
||||
setConfirmDelete(false);
|
||||
handleDelete();
|
||||
}}
|
||||
onCancel={() => setConfirmDelete(false)}
|
||||
/>
|
||||
)}
|
||||
@@ -312,7 +319,9 @@ function CommentNode({
|
||||
value={replyBody}
|
||||
onChange={setReplyBody}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleReply(e);
|
||||
if (
|
||||
e.key === "Enter" && (e.ctrlKey || e.metaKey)
|
||||
) handleReply(e);
|
||||
}}
|
||||
placeholder="Write a reply…"
|
||||
autoResize
|
||||
@@ -348,7 +357,9 @@ function CommentNode({
|
||||
{children.length > 0 && (
|
||||
<ul
|
||||
className="comment-replies"
|
||||
style={depth >= MAX_INDENT_DEPTH ? { paddingLeft: 0, marginLeft: 0, borderLeft: "none" } : undefined}
|
||||
style={depth >= MAX_INDENT_DEPTH
|
||||
? { paddingLeft: 0, marginLeft: 0, borderLeft: "none" }
|
||||
: undefined}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<CommentNode
|
||||
@@ -440,7 +451,9 @@ export function CommentThread({
|
||||
value={topLevelBody}
|
||||
onChange={setTopLevelBody}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleTopLevelSubmit(e);
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
handleTopLevelSubmit(e);
|
||||
}
|
||||
}}
|
||||
placeholder="Add a comment…"
|
||||
autoResize
|
||||
|
||||
@@ -13,7 +13,6 @@ import type {
|
||||
import { deserializeDump, deserializePlaylistMembership } from "../model.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { formatBytes } from "../utils/format.ts";
|
||||
import { dumpUrl } from "../utils/urls.ts";
|
||||
import RichContentCard from "./RichContentCard.tsx";
|
||||
import { MediaPlayer } from "./MediaPlayer.tsx";
|
||||
|
||||
@@ -71,23 +71,21 @@ export function FileDropZone({
|
||||
<div className="fdz-wrapper">
|
||||
{label && <span className="fdz-label">{label}</span>}
|
||||
<div
|
||||
className={`fdz${dragging ? " fdz--drag" : ""}${disabled ? " fdz--disabled" : ""}${file ? " fdz--filled" : ""}`}
|
||||
className={`fdz${dragging ? " fdz--drag" : ""}${
|
||||
disabled ? " fdz--disabled" : ""
|
||||
}${file ? " fdz--filled" : ""}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={file ? undefined : handleClick}
|
||||
role={file ? undefined : "button"}
|
||||
tabIndex={file || disabled ? undefined : 0}
|
||||
onKeyDown={
|
||||
file || disabled
|
||||
? undefined
|
||||
: (e) => {
|
||||
onKeyDown={file || disabled ? undefined : (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
||||
import { MentionDropdown } from "./MentionDropdown.tsx";
|
||||
import { useMentionAutocomplete } from "../hooks/useMentionAutocomplete.ts";
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ export function useDumpListSync(
|
||||
|
||||
useEffect(() => {
|
||||
if (deletedDumpIds.size === 0) return;
|
||||
setDumpsRef.current((prev) => prev.filter((d) => !deletedDumpIds.has(d.id)));
|
||||
setDumpsRef.current((prev) =>
|
||||
prev.filter((d) => !deletedDumpIds.has(d.id))
|
||||
);
|
||||
}, [deletedDumpIds]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState, type RefObject } from "react";
|
||||
import {
|
||||
type RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { API_URL } from "../config/api.ts";
|
||||
|
||||
export interface UserResult {
|
||||
@@ -70,7 +76,9 @@ export function useMentionAutocomplete(
|
||||
debounceRef.current = setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_URL}/api/users/search?q=${encodeURIComponent(mention.query)}`,
|
||||
`${API_URL}/api/users/search?q=${
|
||||
encodeURIComponent(mention.query)
|
||||
}`,
|
||||
);
|
||||
const body = await res.json();
|
||||
if (body.success && body.data.length > 0) {
|
||||
|
||||
@@ -125,7 +125,10 @@ export function Dump() {
|
||||
if (!el) return;
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
el.classList.add("comment-node--highlight");
|
||||
const t = setTimeout(() => el.classList.remove("comment-node--highlight"), 2000);
|
||||
const t = setTimeout(
|
||||
() => el.classList.remove("comment-node--highlight"),
|
||||
2000,
|
||||
);
|
||||
return () => clearTimeout(t);
|
||||
}, [comments, location.hash]);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Link, useNavigate } from "react-router";
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import type { CreateUrlDumpRequest, RichContent } from "../model.ts";
|
||||
import { useRequiredAuth } from "../hooks/useAuth.ts";
|
||||
import { formatBytes } from "../utils/format.ts";
|
||||
import { dumpUrl } from "../utils/urls.ts";
|
||||
import { PageShell } from "../components/PageShell.tsx";
|
||||
import RichContentCard from "../components/RichContentCard.tsx";
|
||||
|
||||
@@ -97,7 +97,9 @@ function notificationLink(n: Notification): string {
|
||||
return `/dumps/${(data as DumpUpvotedData).dumpId}`;
|
||||
case "user_mentioned": {
|
||||
const d = data as UserMentionedData;
|
||||
if (d.contextType === "comment") return `/dumps/${d.dumpId}#comment-${d.contextId}`;
|
||||
if (d.contextType === "comment") {
|
||||
return `/dumps/${d.dumpId}#comment-${d.contextId}`;
|
||||
}
|
||||
if (d.contextType === "dump") return `/dumps/${d.contextId}`;
|
||||
return `/playlists/${d.contextId}`;
|
||||
}
|
||||
@@ -109,28 +111,64 @@ function notificationContent(n: Notification): React.ReactNode {
|
||||
switch (n.type) {
|
||||
case "user_followed": {
|
||||
const d = data as UserFollowedData;
|
||||
return <><strong>{d.followerUsername}</strong>{" started following you"}</>;
|
||||
return (
|
||||
<>
|
||||
<strong>{d.followerUsername}</strong>
|
||||
{" started following you"}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "playlist_followed": {
|
||||
const d = data as PlaylistFollowedData;
|
||||
return <><strong>{d.followerUsername}</strong>{" followed your playlist "}<strong>{d.playlistTitle}</strong></>;
|
||||
return (
|
||||
<>
|
||||
<strong>{d.followerUsername}</strong>
|
||||
{" followed your playlist "}
|
||||
<strong>{d.playlistTitle}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "user_dump_posted": {
|
||||
const d = data as UserDumpPostedData;
|
||||
return <><strong>{d.dumperUsername}</strong>{" posted "}<strong>{d.dumpTitle}</strong></>;
|
||||
return (
|
||||
<>
|
||||
<strong>{d.dumperUsername}</strong>
|
||||
{" posted "}
|
||||
<strong>{d.dumpTitle}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "playlist_dump_added": {
|
||||
const d = data as PlaylistDumpAddedData;
|
||||
return <><strong>{d.dumpTitle}</strong>{" was added to "}<strong>{d.playlistTitle}</strong></>;
|
||||
return (
|
||||
<>
|
||||
<strong>{d.dumpTitle}</strong>
|
||||
{" was added to "}
|
||||
<strong>{d.playlistTitle}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "dump_upvoted": {
|
||||
const d = data as DumpUpvotedData;
|
||||
return <><strong>{d.voterUsername}</strong>{" upvoted "}<strong>{d.dumpTitle}</strong></>;
|
||||
return (
|
||||
<>
|
||||
<strong>{d.voterUsername}</strong>
|
||||
{" upvoted "}
|
||||
<strong>{d.dumpTitle}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "user_mentioned": {
|
||||
const d = data as UserMentionedData;
|
||||
const where = d.contextTitle || (d.contextType === "comment" ? "a comment" : "a post");
|
||||
return <><strong>{d.mentionerUsername}</strong>{" mentioned you in "}<strong>{where}</strong></>;
|
||||
const where = d.contextTitle ||
|
||||
(d.contextType === "comment" ? "a comment" : "a post");
|
||||
return (
|
||||
<>
|
||||
<strong>{d.mentionerUsername}</strong>
|
||||
{" mentioned you in "}
|
||||
<strong>{where}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +341,9 @@ export function PlaylistDetail() {
|
||||
dumpMap.set(dump.id, dump);
|
||||
const reinserted = order.length > 0
|
||||
? [
|
||||
...order.filter((id) => dumpMap.has(id)).map((id) => dumpMap.get(id)!),
|
||||
...order.filter((id) => dumpMap.has(id)).map((id) =>
|
||||
dumpMap.get(id)!
|
||||
),
|
||||
...prev.playlist.dumps.filter((d) => !new Set(order).has(d.id)),
|
||||
]
|
||||
: [...prev.playlist.dumps, dump];
|
||||
@@ -684,7 +686,9 @@ export function PlaylistDetail() {
|
||||
</time>
|
||||
</Tooltip>
|
||||
{playlist.updatedAt && (
|
||||
<Tooltip text={`Edited ${playlist.updatedAt.toLocaleString()}`}>
|
||||
<Tooltip
|
||||
text={`Edited ${playlist.updatedAt.toLocaleString()}`}
|
||||
>
|
||||
<span className="playlist-edited-label">
|
||||
edited {relativeTime(playlist.updatedAt)}
|
||||
</span>
|
||||
@@ -705,7 +709,9 @@ export function PlaylistDetail() {
|
||||
: (
|
||||
<div
|
||||
className="playlist-dump-list"
|
||||
onDragOver={isOwner ? (e) => e.preventDefault() : undefined}
|
||||
onDragOver={isOwner
|
||||
? (e) => e.preventDefault()
|
||||
: undefined}
|
||||
>
|
||||
{visibleDumps.map((dump) => {
|
||||
const isActive = activeDumpIds.has(dump.id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import type { SubmitEvent } from "react";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import { deserializeAuthResponse } from "../model.ts";
|
||||
|
||||
@@ -77,7 +77,9 @@ export function UserPlaylists() {
|
||||
|
||||
const setCreated = useCallback((fn: (prev: Playlist[]) => Playlist[]) => {
|
||||
setState((s) =>
|
||||
s.status !== "loaded" ? s : { ...s, created: { ...s.created, items: fn(s.created.items) } }
|
||||
s.status !== "loaded"
|
||||
? s
|
||||
: { ...s, created: { ...s.created, items: fn(s.created.items) } }
|
||||
);
|
||||
}, []);
|
||||
usePlaylistListSync(setCreated, {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
@@ -151,7 +157,9 @@ export function UserPublicProfile() {
|
||||
if (next.length < prev.length) {
|
||||
const nextIds = new Set(next.map((d) => d.id));
|
||||
prev.forEach((d, idx) => {
|
||||
if (!nextIds.has(d.id)) removedDumpPositionsRef.current.set(d.id, idx);
|
||||
if (!nextIds.has(d.id)) {
|
||||
removedDumpPositionsRef.current.set(d.id, idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
return { ...s, dumps: { ...s.dumps, items: next } };
|
||||
@@ -180,7 +188,9 @@ export function UserPublicProfile() {
|
||||
if (next.length < prev.length) {
|
||||
const nextIds = new Set(next.map((d) => d.id));
|
||||
prev.forEach((d, idx) => {
|
||||
if (!nextIds.has(d.id)) removedVotePositionsRef.current.set(d.id, idx);
|
||||
if (!nextIds.has(d.id)) {
|
||||
removedVotePositionsRef.current.set(d.id, idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
return { ...s, votes: { ...s.votes, items: next } };
|
||||
@@ -422,7 +432,6 @@ export function UserPublicProfile() {
|
||||
}
|
||||
}, [lastVoteEvent, me, profileUserId]);
|
||||
|
||||
|
||||
// Save scroll position + loaded state to sessionStorage on scroll
|
||||
useEffect(() => {
|
||||
if (state.status !== "loaded") return;
|
||||
|
||||
Reference in New Issue
Block a user