refactor: extract star/notification/badge helpers into database.py and add enrich_items content module
Introduce `update_target_stars`, `get_target_owner_uid`, and `VOTABLE_TARGETS`/`STAR_TARGETS` in database.py; replace inline badge insertion with `award_badge()` and inline notification creation with `create_notification()` across auth, comments, and follow routers; add `enrich_items()` in new content module to centralize post/gist list assembly and remove duplicated enrichment logic from feed and gists routers.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { Toast } from "./Toast.js";
|
||||
|
||||
export class AttachmentUploader {
|
||||
constructor(form) {
|
||||
this.form = form;
|
||||
@@ -172,8 +174,7 @@ export class AttachmentUploader {
|
||||
}
|
||||
|
||||
showError(msg) {
|
||||
this.errorEl.textContent = msg;
|
||||
setTimeout(() => { if (this.errorEl.textContent === msg) this.errorEl.textContent = ""; }, 5000);
|
||||
Toast.flash(this.errorEl, msg, 5000, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Toast } from "./Toast.js";
|
||||
|
||||
export class DomUtils {
|
||||
constructor() {
|
||||
this.initClipboardCopy();
|
||||
@@ -14,9 +16,7 @@ export class DomUtils {
|
||||
if (!source) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(source.textContent);
|
||||
const original = btn.textContent;
|
||||
btn.textContent = "Copied!";
|
||||
setTimeout(() => { btn.textContent = original; }, 2000);
|
||||
Toast.flash(btn, "Copied!", 2000);
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
@@ -32,9 +32,7 @@ export class DomUtils {
|
||||
const url = new URL(btn.dataset.share || window.location.href, window.location.href).href;
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
const original = btn.textContent;
|
||||
btn.textContent = "Copied!";
|
||||
setTimeout(() => { btn.textContent = original; }, 1000);
|
||||
Toast.flash(btn, "Copied!", 1000);
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TextInput } from "./TextInput.js";
|
||||
|
||||
export class EmojiPicker {
|
||||
constructor(textarea) {
|
||||
this.textarea = textarea;
|
||||
@@ -35,15 +37,7 @@ export class EmojiPicker {
|
||||
}
|
||||
|
||||
insert(unicode) {
|
||||
const ta = this.textarea;
|
||||
const start = ta.selectionStart;
|
||||
const end = ta.selectionEnd;
|
||||
const text = ta.value;
|
||||
ta.value = text.substring(0, start) + unicode + text.substring(end);
|
||||
const newPos = start + unicode.length;
|
||||
ta.setSelectionRange(newPos, newPos);
|
||||
ta.focus();
|
||||
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
TextInput.insertAtCursor(this.textarea, unicode);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Http } from "./Http.js";
|
||||
import { Avatar } from "./Avatar.js";
|
||||
import { TextInput } from "./TextInput.js";
|
||||
|
||||
export class MentionInput {
|
||||
constructor(element) {
|
||||
this.input = element;
|
||||
@@ -50,8 +54,7 @@ export class MentionInput {
|
||||
|
||||
async fetch(query) {
|
||||
try {
|
||||
const resp = await fetch("/profile/search?q=" + encodeURIComponent(query));
|
||||
const data = await resp.json();
|
||||
const data = await Http.getJson("/profile/search?q=" + encodeURIComponent(query));
|
||||
const results = data.results || [];
|
||||
if (results.length === 0) {
|
||||
this.dropdown.style.display = "none";
|
||||
@@ -71,7 +74,7 @@ export class MentionInput {
|
||||
item.type = "button";
|
||||
item.className = "mention-dropdown-item";
|
||||
item.dataset.username = r.username;
|
||||
item.innerHTML = '<img src="/avatar/multiavatar/' + encodeURIComponent(r.username) + '?size=24" class="avatar-img" style="width:24px;height:24px;border-radius:50%" alt="" loading="lazy"><span>@' + r.username + '</span>';
|
||||
item.innerHTML = Avatar.imgHtml(r.username) + "<span>@" + r.username + "</span>";
|
||||
item.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
this.insert(r.username);
|
||||
@@ -121,11 +124,7 @@ export class MentionInput {
|
||||
let after = val.substring(this.lastMatch.index + this.lastMatch.query.length + 1);
|
||||
before = before.replace(/@+$/, "");
|
||||
after = after.replace(/^@+/, "");
|
||||
this.input.value = before + "@" + username + " " + after;
|
||||
const newPos = before.length + username.length + 2;
|
||||
this.input.setSelectionRange(newPos, newPos);
|
||||
this.input.focus();
|
||||
this.input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
TextInput.applyValue(this.input, before + "@" + username + " " + after, before.length + username.length + 2);
|
||||
this.dropdown.style.display = "none";
|
||||
this.lastMatch = null;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Http } from "./Http.js";
|
||||
import { Avatar } from "./Avatar.js";
|
||||
|
||||
export class MessageSearch {
|
||||
constructor() {
|
||||
this.initMessageSearch();
|
||||
@@ -26,8 +29,7 @@ export class MessageSearch {
|
||||
}
|
||||
debounceTimer = setTimeout(async () => {
|
||||
try {
|
||||
const resp = await fetch(`/messages/search?q=${encodeURIComponent(q)}`);
|
||||
const data = await resp.json();
|
||||
const data = await Http.getJson(`/messages/search?q=${encodeURIComponent(q)}`);
|
||||
const results = data.results || [];
|
||||
if (results.length === 0) {
|
||||
dropdown.style.display = "none";
|
||||
@@ -38,7 +40,7 @@ export class MessageSearch {
|
||||
const item = document.createElement("a");
|
||||
item.className = "search-dropdown-item";
|
||||
item.href = `/messages?with_uid=${r.uid}`;
|
||||
item.innerHTML = `<img src="/avatar/multiavatar/${encodeURIComponent(r.username)}?size=24" class="avatar-img" style="width:24px;height:24px;border-radius:50%" alt="" loading="lazy"><span>${r.username}</span>`;
|
||||
item.innerHTML = `${Avatar.imgHtml(r.username)}<span>${r.username}</span>`;
|
||||
dropdown.appendChild(item);
|
||||
}
|
||||
dropdown.style.display = "block";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Http } from "./Http.js";
|
||||
|
||||
class ServiceMonitor {
|
||||
constructor() {
|
||||
this.pollInterval = 5000;
|
||||
@@ -29,8 +31,7 @@ class ServiceMonitor {
|
||||
|
||||
async pollServices() {
|
||||
try {
|
||||
const resp = await fetch("/admin/services/data");
|
||||
const data = await resp.json();
|
||||
const data = await Http.getJson("/admin/services/data");
|
||||
const container = document.getElementById("services-list");
|
||||
if (!container) return;
|
||||
for (const svc of data.services) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Http } from "./Http.js";
|
||||
|
||||
export class VoteManager {
|
||||
constructor() {
|
||||
this.initVoteButtons();
|
||||
@@ -6,37 +8,22 @@ export class VoteManager {
|
||||
|
||||
initVoteButtons() {
|
||||
document.querySelectorAll(".post-action-btn[data-vote]").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
btn.addEventListener("click", () => {
|
||||
const targetUid = btn.dataset.target;
|
||||
const targetType = btn.dataset.type || "post";
|
||||
const value = btn.dataset.vote;
|
||||
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = `/votes/${targetType}/${targetUid}`;
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = "value";
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
Http.postForm(`/votes/${targetType}/${targetUid}`, { value: btn.dataset.vote });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initNotificationDismiss() {
|
||||
document.querySelectorAll(".notification-dismiss").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
btn.addEventListener("click", () => {
|
||||
const uid = btn.dataset.uid;
|
||||
if (!uid) {
|
||||
return;
|
||||
}
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = `/notifications/mark-read/${uid}`;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
Http.postForm(`/notifications/mark-read/${uid}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user