fix: migrate CI branch references from master to main and add comments created_at index
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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">📎</span><span>${file.name}</span>`;
|
||||
div.appendChild(iconDiv);
|
||||
}
|
||||
const remove = document.createElement("button");
|
||||
remove.type = "button";
|
||||
remove.className = "attachment-remove";
|
||||
remove.innerHTML = "×";
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user