fix: migrate CI branch references from master to main and add comments created_at index

This commit is contained in:
2026-05-13 19:17:57 +00:00
parent aa88f18c03
commit 4958c23c0d
33 changed files with 770 additions and 203 deletions
+25 -33
View File
@@ -1,48 +1,33 @@
.attachment-upload-zone {
border: 2px dashed var(--border-light);
.attachment-upload-container button.attachment-upload-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.375rem 0.5rem;
border-radius: var(--radius);
padding: 1.5rem 1rem;
text-align: center;
font-size: 1.125rem;
color: var(--text-muted);
cursor: pointer;
transition: all 0.2s;
background: var(--bg-input);
position: relative;
background: none;
border: 1px solid transparent;
line-height: 1;
}
.attachment-upload-zone:hover {
.attachment-upload-container button.attachment-upload-btn:hover {
background: var(--bg-card-hover);
color: var(--text-primary);
}
.attachment-upload-container button.attachment-upload-btn.dragover {
border-color: var(--accent);
background: var(--accent-light);
}
.attachment-upload-zone.dragover {
border-color: var(--accent);
background: var(--accent-light);
border-style: solid;
}
.attachment-upload-zone input[type="file"] {
.attachment-upload-container button.attachment-upload-btn input[type="file"] {
display: none;
}
.attachment-upload-zone .upload-icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
display: block;
}
.attachment-upload-zone .upload-text {
font-size: 0.8125rem;
color: var(--text-muted);
}
.attachment-upload-zone .upload-hint {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 0.375rem;
opacity: 0.7;
}
.attachment-upload-zone.uploading {
.attachment-upload-container button.attachment-upload-btn.uploading {
pointer-events: none;
opacity: 0.6;
}
@@ -52,6 +37,13 @@
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
flex: 0 0 100%;
width: 100%;
}
.attachment-preview-list:empty,
.attachment-upload-error:empty {
display: none;
}
.attachment-preview {
+7 -4
View File
@@ -65,7 +65,7 @@
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.25rem;
padding: 1rem;
transition: all 0.2s;
}
@@ -103,7 +103,9 @@
font-size: 0.6875rem;
color: var(--text-muted);
margin-left: auto;
padding-left: 0.75rem;
white-space: nowrap;
line-height: 1.5;
}
.post-topic {
@@ -122,7 +124,7 @@
font-size: 0.875rem;
color: var(--text-secondary);
line-height: 1.65;
margin-bottom: 0.75rem;
margin-bottom: 1rem;
word-break: break-word;
display: -webkit-box;
-webkit-line-clamp: 4;
@@ -133,12 +135,13 @@
.post-actions {
display: flex;
align-items: center;
gap: 0.25rem;
padding-top: 0.75rem;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
.post-action-btn {
line-height: 1;
display: inline-flex;
align-items: center;
gap: 0.375rem;
+1
View File
@@ -172,6 +172,7 @@
padding: 1rem;
border-top: 1px solid var(--border);
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
+30 -12
View File
@@ -7,8 +7,8 @@
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.post-detail-header {
@@ -33,6 +33,8 @@
font-size: 0.8125rem;
color: var(--text-muted);
margin-left: auto;
padding-left: 0.75rem;
line-height: 1.5;
}
.post-detail-title {
@@ -45,13 +47,14 @@
font-size: 0.9375rem;
color: var(--text-secondary);
line-height: 1.7;
margin-top: 0.625rem;
margin-bottom: 1rem;
}
.post-detail-actions {
display: flex;
align-items: center;
gap: 0.5rem;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
@@ -60,7 +63,7 @@
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.5rem;
padding: 1rem;
}
.comments-section h3 {
@@ -72,7 +75,7 @@
.comment {
display: flex;
gap: 0.75rem;
padding: 0.75rem 0;
padding: 1rem 0;
position: relative;
}
@@ -160,23 +163,37 @@
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
align-items: center;
}
.comment-form > a {
flex-shrink: 0;
line-height: 0;
}
.comment-form textarea {
flex: 1;
min-width: 0;
min-height: 44px;
max-height: 120px;
}
.comment-form-actions {
display: flex;
align-items: flex-end;
gap: 0.25rem;
flex-wrap: wrap;
align-items: center;
flex-shrink: 0;
}
.comment-form-actions button {
padding: 0.5rem;
padding: 0.375rem 0.5rem;
font-size: 1rem;
line-height: 1;
color: var(--text-muted);
background: none;
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: color 0.2s;
}
@@ -184,16 +201,16 @@
color: var(--text-primary);
}
.comment-form-submit {
padding: 0.5rem 1rem;
button.comment-form-submit {
padding: 0.375rem 0.5rem;
background: var(--accent);
color: #fff;
border-radius: var(--radius);
font-weight: 600;
font-size: 0.8125rem;
line-height: 1.4;
}
.comment-form-submit:hover {
button.comment-form-submit:hover {
background: var(--accent-hover);
}
@@ -210,6 +227,7 @@
background: none;
border: none;
cursor: pointer;
line-height: 1;
}
.post-action-btn:hover {
+2 -3
View File
@@ -22,7 +22,7 @@ class Application {
this.initStopPropagation();
this.initMessageThread();
this.initPlatformTags();
this.initAttachmentUploaders();
this.initAttachmentManagers();
}
loadCSS(href) {
@@ -253,8 +253,7 @@ class Application {
}
initCommentReply() {
document.querySelectorAll(".comment-action-btn").forEach((btn) => {
if (btn.textContent.trim() !== "Reply") return;
document.querySelectorAll("[data-action='reply']").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
const comment = btn.closest(".comment");
+179
View File
@@ -0,0 +1,179 @@
class AttachmentUploader {
constructor(form) {
this.form = form;
this.attachments = [];
this.maxSize = parseInt(form.dataset.maxSize || "10", 10) * 1024 * 1024;
this.maxFiles = parseInt(form.dataset.maxFiles || "10", 10);
this.allowedTypes = (form.dataset.allowedTypes || "").split(",").map((t) => t.trim()).filter(Boolean);
this.container = form.querySelector(".attachment-upload-container");
if (!this.container) return;
this.uploadedUidsInput = null;
this.initZone();
}
initZone() {
this.button = document.createElement("button");
this.button.type = "button";
this.button.className = "attachment-upload-btn";
this.button.innerHTML = "📎";
this.button.title = "Attach files";
const input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.addEventListener("change", () => this.handleFiles(input.files));
this.button.appendChild(input);
this.button.addEventListener("click", (e) => {
e.preventDefault();
input.click();
});
this.button.addEventListener("dragover", (e) => {
e.preventDefault();
this.button.classList.add("dragover");
});
this.button.addEventListener("dragleave", () => {
this.button.classList.remove("dragover");
});
this.button.addEventListener("drop", (e) => {
e.preventDefault();
this.button.classList.remove("dragover");
this.handleFiles(e.dataTransfer.files);
});
this.previewList = document.createElement("div");
this.previewList.className = "attachment-preview-list";
this.errorEl = document.createElement("div");
this.errorEl.className = "attachment-upload-error";
this.container.appendChild(this.button);
this.container.after(this.errorEl);
this.container.after(this.previewList);
this.uploadedUidsInput = document.createElement("input");
this.uploadedUidsInput.type = "hidden";
this.uploadedUidsInput.name = "attachment_uids";
this.form.appendChild(this.uploadedUidsInput);
}
async handleFiles(files) {
this.errorEl.textContent = "";
const remaining = this.maxFiles - this.attachments.length;
if (files.length > remaining) {
this.showError(`You can only add ${remaining} more file(s).`);
return;
}
for (const file of files) {
if (file.size > this.maxSize) {
this.showError(`"${file.name}" exceeds the ${this.form.dataset.maxSize || 10}MB limit.`);
continue;
}
if (this.allowedTypes.length > 0) {
const ext = "." + file.name.split(".").pop().toLowerCase();
if (!this.allowedTypes.includes(ext)) {
this.showError(`"${file.name}" type is not allowed.`);
continue;
}
}
await this.uploadFile(file);
}
}
async uploadFile(file) {
this.button.classList.add("uploading");
const preview = this.createPreview(file);
this.previewList.appendChild(preview);
const progressBar = preview.querySelector(".attachment-progress-bar");
const formData = new FormData();
formData.append("file", file);
try {
const xhr = new XMLHttpRequest();
xhr.open("POST", "/uploads/upload");
xhr.upload.addEventListener("progress", (e) => {
if (e.lengthComputable && progressBar) {
const pct = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = pct + "%";
}
});
const result = await new Promise((resolve, reject) => {
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try { resolve(JSON.parse(xhr.responseText)); }
catch { reject(new Error("Invalid response")); }
} else {
reject(new Error(xhr.responseText || "Upload failed"));
}
};
xhr.onerror = () => reject(new Error("Network error"));
xhr.send(formData);
});
if (result && result.uid) {
this.attachments.push(result);
this.updateUids();
preview.dataset.uid = result.uid;
if (result.thumbnail_url) {
const img = preview.querySelector("img");
if (img) img.src = result.thumbnail_url;
}
}
} catch (err) {
this.showError(err.message);
preview.remove();
} finally {
this.button.classList.remove("uploading");
}
}
createPreview(file) {
const div = document.createElement("div");
div.className = "attachment-preview";
const isImage = file.type && file.type.startsWith("image/");
if (isImage) {
const img = document.createElement("img");
img.src = URL.createObjectURL(file);
img.alt = file.name;
div.appendChild(img);
} else {
const iconDiv = document.createElement("div");
iconDiv.className = "file-icon";
iconDiv.innerHTML = `<span class="icon">&#x1F4CE;</span><span>${file.name}</span>`;
div.appendChild(iconDiv);
}
const remove = document.createElement("button");
remove.type = "button";
remove.className = "attachment-remove";
remove.innerHTML = "&times;";
remove.addEventListener("click", () => {
const idx = this.attachments.findIndex((a) => a.uid === div.dataset.uid);
if (idx !== -1) {
this.attachments.splice(idx, 1);
this.updateUids();
}
div.remove();
});
div.appendChild(remove);
const progress = document.createElement("div");
progress.className = "attachment-progress";
progress.innerHTML = '<div class="attachment-progress-bar" style="width:0%"></div>';
div.appendChild(progress);
return div;
}
updateUids() {
const uids = this.attachments.map((a) => a.uid);
this.uploadedUidsInput.value = uids.join(",");
}
showError(msg) {
this.errorEl.textContent = msg;
setTimeout(() => { if (this.errorEl.textContent === msg) this.errorEl.textContent = ""; }, 5000);
}
}
window.AttachmentUploader = AttachmentUploader;
+5 -3
View File
@@ -1,13 +1,15 @@
class GistEditor {
constructor() {
constructor(textareaId, langSelectId) {
this.editor = null;
this.textareaId = textareaId || "gist-source-editor";
this.langSelectId = langSelectId || "gist-language";
this.init();
}
init() {
if (typeof CodeMirror === "undefined") return;
const textarea = document.getElementById("gist-source-editor");
const textarea = document.getElementById(this.textareaId);
if (!textarea || textarea.dataset.cminit) return;
textarea.dataset.cminit = "1";
@@ -31,7 +33,7 @@ class GistEditor {
this.editor.setSize(null, 400);
const langSelect = document.getElementById("gist-language");
const langSelect = document.getElementById(this.langSelectId);
if (langSelect) {
langSelect.addEventListener("change", () => {
let mode = langSelect.value;