PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.6
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.6
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / assets / js / widget-recent-comments.js

widget-recent-comments.js in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.6, at assets/js/widget-recent-comments.js

200 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function() {
2 "use strict";
3 const NONCE_HEADER = "X-WP-Nonce";
4 function injectRestNonce(input, init) {
5 const nonce = readRestNonce();
6 if (!nonce) {
7 return init;
8 }
9 const url = resolveUrl(input);
10 if (!url || !isSameOriginRestUrl(url)) {
11 return init;
12 }
13 const baseHeaders = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
14 const headers = new Headers(baseHeaders ?? {});
15 if (headers.has(NONCE_HEADER)) {
16 return init;
17 }
18 headers.set(NONCE_HEADER, nonce);
19 return { ...init ?? {}, headers };
20 }
21 function readRestNonce() {
22 if (typeof window === "undefined") {
23 return void 0;
24 }
25 const cfg = window.desktopModeConfig;
26 const value = cfg?.restNonce;
27 return typeof value === "string" && value.length > 0 ? value : void 0;
28 }
29 function resolveUrl(input) {
30 try {
31 const base = typeof window !== "undefined" && window.location ? window.location.href : void 0;
32 if (typeof input === "string") {
33 return new URL(input, base);
34 }
35 if (input instanceof URL) {
36 return input;
37 }
38 if (typeof Request !== "undefined" && input instanceof Request) {
39 return new URL(input.url, base);
40 }
41 return null;
42 } catch {
43 return null;
44 }
45 }
46 function isSameOriginRestUrl(url) {
47 if (typeof window === "undefined" || !window.location || url.origin !== window.location.origin) {
48 return false;
49 }
50 if (url.pathname.includes("/wp-json/")) {
51 return true;
52 }
53 if (url.searchParams.has("rest_route")) {
54 return true;
55 }
56 return false;
57 }
58 function trackedFetch(input, init, opts = {}) {
59 const fn = window.wp?.desktop?.fetch;
60 if (typeof fn === "function") {
61 return fn(input, init, opts);
62 }
63 const finalInit = injectRestNonce(input, init);
64 return fetch(input, finalInit);
65 }
66 const WIDGET_ID = "desktop-mode/recent-comments";
67 const REFRESH_MS = 6e4;
68 const LIMIT = 8;
69 const STATUS_META = {
70 approved: { label: "Approved", color: "#22c55e" },
71 hold: { label: "Pending", color: "#f59e0b" },
72 spam: { label: "Spam", color: "#ef4444" },
73 trash: { label: "Trash", color: "#9ca3af" }
74 };
75 function timeAgo(isoUtc) {
76 const ts = isoUtc.endsWith("Z") ? isoUtc : isoUtc + "Z";
77 const secs = Math.floor((Date.now() - new Date(ts).getTime()) / 1e3);
78 if (secs < 60) {
79 return secs + "s ago";
80 }
81 if (secs < 3600) {
82 return Math.floor(secs / 60) + "m ago";
83 }
84 if (secs < 86400) {
85 return Math.floor(secs / 3600) + "h ago";
86 }
87 return Math.floor(secs / 86400) + "d ago";
88 }
89 async function fetchComments() {
90 const root = window.wpApiSettings?.root ?? "/wp-json/";
91 const res = await trackedFetch(
92 root.replace(/\/$/, "") + `/wp/v2/comments?per_page=${LIMIT}&orderby=date&order=desc&_embed=up`,
93 { credentials: "same-origin" },
94 { source: "desktop-mode/recent-comments", silent: true }
95 );
96 if (!res.ok) {
97 throw new Error(`HTTP ${res.status}`);
98 }
99 return res.json();
100 }
101 function render(container, comments, error) {
102 container.innerHTML = "";
103 const header = document.createElement("div");
104 header.className = "dm-comments__header";
105 const title = document.createElement("span");
106 title.className = "dm-comments__title";
107 title.textContent = "Recent Comments";
108 const badge = document.createElement("span");
109 badge.className = "dm-comments__badge";
110 const pending = comments ? comments.filter((c) => c.status === "hold").length : 0;
111 if (pending > 0) {
112 badge.textContent = pending + " pending";
113 badge.classList.add("dm-comments__badge--visible");
114 }
115 header.appendChild(title);
116 header.appendChild(badge);
117 container.appendChild(header);
118 if (error) {
119 const err = document.createElement("div");
120 err.className = "dm-comments__error";
121 err.textContent = "Could not load comments.";
122 container.appendChild(err);
123 return;
124 }
125 if (!comments || comments.length === 0) {
126 const empty = document.createElement("div");
127 empty.className = "dm-comments__empty";
128 empty.textContent = "No comments yet.";
129 container.appendChild(empty);
130 return;
131 }
132 const list = document.createElement("div");
133 list.className = "dm-comments__list";
134 for (const c of comments) {
135 const row = document.createElement("div");
136 row.className = "dm-comments__row";
137 const avatar = document.createElement("div");
138 avatar.className = "dm-comments__avatar";
139 avatar.textContent = (c.author_name || "?").trim().charAt(0).toUpperCase();
140 const body = document.createElement("div");
141 body.className = "dm-comments__body";
142 const meta = document.createElement("div");
143 meta.className = "dm-comments__meta";
144 const author = document.createElement("span");
145 author.className = "dm-comments__author";
146 author.textContent = c.author_name || "Anonymous";
147 const sm = STATUS_META[c.status] ?? STATUS_META.hold;
148 const statusEl = document.createElement("span");
149 statusEl.className = "dm-comments__status";
150 statusEl.style.background = sm.color;
151 statusEl.textContent = sm.label;
152 const time = document.createElement("span");
153 time.className = "dm-comments__time";
154 time.textContent = timeAgo(c.date_gmt);
155 meta.appendChild(author);
156 meta.appendChild(statusEl);
157 meta.appendChild(time);
158 const postEl = document.createElement("div");
159 postEl.className = "dm-comments__post";
160 postEl.textContent = "" + (c._embedded?.up?.[0]?.title?.rendered ?? `Post #${c.post}`);
161 body.appendChild(meta);
162 body.appendChild(postEl);
163 row.appendChild(avatar);
164 row.appendChild(body);
165 list.appendChild(row);
166 }
167 container.appendChild(list);
168 }
169 const mount = async (container, _ctx) => {
170 let destroyed = false;
171 let intervalId = null;
172 const refresh = async () => {
173 if (destroyed) {
174 return;
175 }
176 try {
177 const comments = await fetchComments();
178 if (!destroyed) {
179 render(container, comments, false);
180 }
181 } catch {
182 if (!destroyed) {
183 render(container, null, true);
184 }
185 }
186 };
187 await refresh();
188 intervalId = setInterval(refresh, REFRESH_MS);
189 return () => {
190 destroyed = true;
191 if (intervalId !== null) {
192 clearInterval(intervalId);
193 }
194 };
195 };
196 const w = window;
197 w.desktopModeWidgets = w.desktopModeWidgets ?? {};
198 w.desktopModeWidgets[WIDGET_ID] = mount;
199 })();
200