mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-19 06:07:21 +02:00
fix: make dashboard UI consistent and compact on mobile
This commit is contained in:
1 parent
adc9dbd439
commit
282efdc16e
52 files changed
+2177
-712
No files matched your search
+368
-125
@@ -1,8 +1,21 @@
|
||||
import { reactive } from "vue";
|
||||
import { reactive, nextTick } from "vue";
|
||||
import { createTimers, createListeners } from "./state.js";
|
||||
import { formDraft } from "./drafts.js";
|
||||
|
||||
function previewTabKeydown(event) {
|
||||
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
||||
const tabs = [...event.currentTarget.closest('[role="tablist"]').querySelectorAll('[role="tab"]')];
|
||||
const index = tabs.indexOf(event.currentTarget);
|
||||
const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1
|
||||
: (index + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length;
|
||||
event.preventDefault();
|
||||
tabs[next]?.focus();
|
||||
tabs[next]?.click();
|
||||
}
|
||||
|
||||
// Page setup functions run inside Vue effect scopes.
|
||||
export const mainController = function (state, http, location, timeout) {
|
||||
state.previewTabKeydown = previewTabKeydown;
|
||||
state.title = "Main";
|
||||
state.user = { status: "connection" };
|
||||
state.site_options;
|
||||
@@ -51,12 +64,6 @@ export const mainController = function (state, http, location, timeout) {
|
||||
document.head.append(link);
|
||||
$(`link[href='${darkPrismLink}']`).remove();
|
||||
}
|
||||
// Update Ko-fi floating button to match theme
|
||||
var kofiBtn = document.querySelector(".floatingchat-container-wrap-mo498 .floating-chat-kofi-text-container-wrap");
|
||||
if (kofiBtn) {
|
||||
kofiBtn.style.backgroundColor = on ? "#FAF9F6" : "#1A1815";
|
||||
kofiBtn.style.color = on ? "#1A1815" : "#FAF9F6";
|
||||
}
|
||||
state.emit("dark-mode", on);
|
||||
};
|
||||
|
||||
@@ -111,7 +118,23 @@ export const mainController = function (state, http, location, timeout) {
|
||||
};
|
||||
|
||||
export const faqController = function (state, http) {
|
||||
const listen = createListeners();};
|
||||
const listen = createListeners();
|
||||
const timers = createTimers();
|
||||
const revealAnswer = () => {
|
||||
const id = window.location.hash.slice(1);
|
||||
const answer = document.getElementById(id);
|
||||
if (!answer?.classList.contains("panel-collapse")) return;
|
||||
$(answer).collapse("show");
|
||||
const heading = document.getElementById(answer.getAttribute("aria-labelledby"));
|
||||
heading?.setAttribute("tabindex", "-1");
|
||||
heading?.focus({ preventScroll: true });
|
||||
heading?.scrollIntoView?.({ block: "start" });
|
||||
};
|
||||
// Router fragments do not emit hashchange, and answers mount after navigation.
|
||||
state.on("routeUpdate", () => timers.timeout(revealAnswer, 0));
|
||||
listen(window, "hashchange", revealAnswer);
|
||||
timers.timeout(revealAnswer, 0);
|
||||
};
|
||||
|
||||
export const profileController = function (state, http, translate, timeout, quotaService) {
|
||||
state.terms = "";
|
||||
@@ -132,6 +155,8 @@ export const profileController = function (state, http, translate, timeout, quot
|
||||
state.quota = quota;
|
||||
}, console.error);
|
||||
|
||||
const draft = formDraft(state, ["terms", "options"], "profile");
|
||||
state.discardDraft = () => draft.discard();
|
||||
function getDefault() {
|
||||
http.get("/api/user/default").then((res) => {
|
||||
const data = res.data || {};
|
||||
@@ -139,6 +164,7 @@ export const profileController = function (state, http, translate, timeout, quot
|
||||
state.terms = data.terms.join("\n");
|
||||
}
|
||||
state.options = Object.assign({}, state.options, data.options);
|
||||
draft.restore();
|
||||
});
|
||||
}
|
||||
getDefault();
|
||||
@@ -146,6 +172,8 @@ export const profileController = function (state, http, translate, timeout, quot
|
||||
let savedTimer = null;
|
||||
state.saveDefault = ($event) => {
|
||||
if ($event && $event.preventDefault) $event.preventDefault();
|
||||
if (state.saving) return;
|
||||
const submitted = draft.checkpoint();
|
||||
const params = {
|
||||
terms: state.terms
|
||||
.split("\n")
|
||||
@@ -157,9 +185,9 @@ export const profileController = function (state, http, translate, timeout, quot
|
||||
state.error = null;
|
||||
http.post("/api/user/default", params).then(
|
||||
() => {
|
||||
getDefault();
|
||||
draft.accept(submitted);
|
||||
state.saving = false;
|
||||
state.message = "Saved";
|
||||
state.message = state.unsavedChanges ? null : "Saved";
|
||||
if (savedTimer) timeout.cancel(savedTimer);
|
||||
savedTimer = timeout(() => {
|
||||
state.message = null;
|
||||
@@ -168,8 +196,9 @@ export const profileController = function (state, http, translate, timeout, quot
|
||||
(error) => {
|
||||
state.saving = false;
|
||||
const code = error && error.data && error.data.error;
|
||||
translate("ERRORS." + code).then((translation) => {
|
||||
state.error = translation;
|
||||
const key = "ERRORS." + code;
|
||||
translate(key).then((translation) => {
|
||||
state.error = translation && translation !== key ? translation : "Unable to save your defaults. Please try again.";
|
||||
}, () => {
|
||||
state.error = "Unable to save your defaults. Please try again.";
|
||||
});
|
||||
@@ -410,6 +439,88 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
}, 250);
|
||||
|
||||
state.quotaExpanded = false;
|
||||
state.closeQuota = () => {
|
||||
state.quotaExpanded = false;
|
||||
nextTick(() => document.querySelector(".dashboard-quota > summary")?.focus());
|
||||
};
|
||||
createListeners()(document, "click", event => {
|
||||
if (state.quotaExpanded && !event.target.closest?.(".dashboard-quota")) state.quotaExpanded = false;
|
||||
});
|
||||
state.filtersExpanded = false;
|
||||
state.projectView = "all";
|
||||
state.selectedProject = null;
|
||||
state.projectName = "";
|
||||
state.nameSaving = false;
|
||||
state.nameError = "";
|
||||
state.nameMessage = "";
|
||||
state.needsAttention = item => item.status === "error" || item._broken
|
||||
|| (statusKey(item.status) === "progress" && item._stale);
|
||||
state.attentionCount = () => state.items.filter(state.needsAttention).length;
|
||||
state.setProjectView = view => {
|
||||
state.projectView = view;
|
||||
if (view === "attention") {
|
||||
state.filters.status.error = true;
|
||||
state.filters.status.progress = true;
|
||||
}
|
||||
};
|
||||
state.projectDetailsId = item => "project-details-" + encodeURIComponent(item._type + ":" + (item._id || item._source || "unknown"));
|
||||
state.showProjectDetails = async item => {
|
||||
state.selectedProject = item;
|
||||
state.projectName = item.projectName || "";
|
||||
state.nameError = "";
|
||||
state.nameMessage = "";
|
||||
await nextTick();
|
||||
const panel = document.getElementById(state.projectDetailsId(item));
|
||||
panel?.focus();
|
||||
panel?.scrollIntoView?.({ block: "nearest" });
|
||||
};
|
||||
state.closeProjectDetails = item => {
|
||||
state.selectedProject = null;
|
||||
nextTick(() => document.querySelector(`[data-project-actions="${encodeURIComponent(item._type + ":" + item._id)}"]`)?.focus());
|
||||
};
|
||||
state.saveProjectName = async item => {
|
||||
if (state.nameSaving || !item._id) return;
|
||||
const name = state.projectName.trim();
|
||||
if (name.length > 100) { state.nameError = "Use 100 characters or fewer."; return; }
|
||||
state.nameSaving = true;
|
||||
state.nameError = "";
|
||||
state.nameMessage = "";
|
||||
try {
|
||||
const res = await http.post("/api/user/project-name", { type: item._type, id: item._id, name });
|
||||
item.projectName = res.data.name;
|
||||
item._label = item.projectName || item._fallbackLabel;
|
||||
if (state.selectedProject === item) state.nameMessage = name ? "Project name saved." : "Default project name restored.";
|
||||
} catch (_) {
|
||||
if (state.selectedProject === item) state.nameError = "The project name could not be saved. Try again.";
|
||||
} finally { state.nameSaving = false; }
|
||||
};
|
||||
state.copyingId = null;
|
||||
state.copyLink = async (item) => {
|
||||
if (!item._viewUrl || state.copyingId) return;
|
||||
const link = new URL(item._viewUrl, window.location.origin).href;
|
||||
state.copyingId = item._type + ":" + item._id;
|
||||
try {
|
||||
if (window.navigator.clipboard?.writeText) {
|
||||
await window.navigator.clipboard.writeText(link);
|
||||
} else {
|
||||
const field = document.createElement("textarea");
|
||||
field.value = link;
|
||||
field.setAttribute("readonly", "");
|
||||
field.style.position = "fixed";
|
||||
field.style.opacity = "0";
|
||||
const focused = document.activeElement;
|
||||
document.body.appendChild(field);
|
||||
try {
|
||||
field.select();
|
||||
if (!document.execCommand?.("copy")) throw Error("Copy unavailable");
|
||||
} finally { field.remove(); focused?.focus(); }
|
||||
}
|
||||
state.addToast({ title: "Link copied", body: "The anonymous link is ready to share." });
|
||||
} catch {
|
||||
state.addToast({ title: "Copy this anonymous link", body: link });
|
||||
} finally { state.copyingId = null; }
|
||||
};
|
||||
state.items = [];
|
||||
state.search = "";
|
||||
state.loading = true;
|
||||
@@ -458,7 +569,8 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
// `orderBy` is kept as the Angular orderBy expression ("-field" for
|
||||
// descending) so saved preferences stay compatible.
|
||||
const sortFields = {
|
||||
_name: { label: "Name", defaultDesc: false },
|
||||
_name: { label: "Anonymous ID", defaultDesc: false },
|
||||
_label: { label: "Project", defaultDesc: false },
|
||||
anonymizeDate: { label: "Anonymize date", defaultDesc: true },
|
||||
status: { label: "Status", defaultDesc: false },
|
||||
lastView: { label: "Last view", defaultDesc: true },
|
||||
@@ -525,6 +637,8 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
item._source = source;
|
||||
item._broken = !id;
|
||||
item._name = id || source || "(unnamed)";
|
||||
item._fallbackLabel = item._type === "gist" ? "Gist " + (id || source || "without an ID") : source || name || item._name;
|
||||
item._label = item.projectName || item._fallbackLabel;
|
||||
item._editUrl = id ? editUrl : null;
|
||||
item._viewUrl = id ? viewUrl : null;
|
||||
if (item._broken) {
|
||||
@@ -597,7 +711,7 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
pr,
|
||||
pr.pullRequestId,
|
||||
pr.pullRequestId,
|
||||
src.repositoryFullName + "#" + src.pullRequestId,
|
||||
src.repositoryFullName ? src.repositoryFullName + "#" + src.pullRequestId : undefined,
|
||||
"/pull-request-anonymize/" + pr.pullRequestId,
|
||||
"/pr/" + pr.pullRequestId + "/"
|
||||
)
|
||||
@@ -637,11 +751,16 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
state.hasHiddenStatus = () => state.hiddenStatusCount() > 0;
|
||||
|
||||
state.hasActiveFilters = () =>
|
||||
state.projectView !== "all" ||
|
||||
state.typeFilter !== "all" ||
|
||||
state.search.trim().length > 0 ||
|
||||
Object.keys(state.filters.status).some((k) => state.filters.status[k] === false);
|
||||
|
||||
state.showClearFilters = () => state.search.trim().length > 0
|
||||
|| state.typeFilter !== "all" || state.hiddenStatusCount() > 1;
|
||||
|
||||
state.clearFilters = () => {
|
||||
state.projectView = "all";
|
||||
state.typeFilter = "all";
|
||||
state.search = "";
|
||||
Object.keys(state.filters.status).forEach((k) => {
|
||||
@@ -655,6 +774,8 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
if (item._type === "repo" && item.repoId == repoId) {
|
||||
item.status = res.data.status;
|
||||
item.statusMessage = res.data.statusMessage;
|
||||
item._statusKey = statusKey(item.status);
|
||||
if (item._statusKey !== "progress") item._stale = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -777,10 +898,12 @@ export const unifiedDashboardController = function (state, http, location, promi
|
||||
};
|
||||
|
||||
state.itemFilter = (item) => {
|
||||
if (state.projectView === "attention" && !state.needsAttention(item)) return false;
|
||||
if (state.typeFilter !== "all" && item._type !== state.typeFilter) return false;
|
||||
if (state.filters.status[item._statusKey] === false) return false;
|
||||
const needle = state.search.trim().toLowerCase();
|
||||
if (needle.length == 0) return true;
|
||||
if (item._label && String(item._label).toLowerCase().indexOf(needle) > -1) return true;
|
||||
if (item._source && String(item._source).toLowerCase().indexOf(needle) > -1) return true;
|
||||
if (item._id && String(item._id).toLowerCase().indexOf(needle) > -1) return true;
|
||||
if (item.conference && String(item.conference).toLowerCase().indexOf(needle) > -1) return true;
|
||||
@@ -894,6 +1017,56 @@ export const statusController = function (state, http, params) {
|
||||
export const anonymizeController = function (state, http, html, params, location, translate, timeout) {
|
||||
// Unified state
|
||||
state.sourceUrl = "";
|
||||
state.workspaceOpen = false;
|
||||
state.sourceUrlError = "";
|
||||
state.previewStatus = "idle";
|
||||
state.sourceLoading = false;
|
||||
state.mobilePane = "settings";
|
||||
const workspaceMedia = window.matchMedia?.("(max-width: 767px)");
|
||||
state.mobileWorkspace = !!workspaceMedia?.matches;
|
||||
if (workspaceMedia?.addEventListener) createListeners()(workspaceMedia, "change", event => { state.mobileWorkspace = event.matches; });
|
||||
state.selectMobilePane = (pane) => { state.mobilePane = pane; };
|
||||
let sourceGeneration = 0;
|
||||
let sourceDisposed = false;
|
||||
const sourceRequests = new Set();
|
||||
const isCurrentSource = (request, branch = false) => !sourceDisposed
|
||||
&& request.generation === sourceGeneration
|
||||
&& request.url === state.sourceUrl.trim()
|
||||
&& request.connection === state.githubConnection
|
||||
&& (!branch || request.branch === state.source.branch);
|
||||
const beginSourceRequest = () => {
|
||||
const request = { generation: sourceGeneration, url: state.sourceUrl.trim(), connection: state.githubConnection, branch: state.source.branch };
|
||||
sourceRequests.add(request);
|
||||
state.sourceLoading = true;
|
||||
return request;
|
||||
};
|
||||
const finishSourceRequest = (request) => {
|
||||
sourceRequests.delete(request);
|
||||
if (!sourceDisposed) state.sourceLoading = [...sourceRequests].some(request => isCurrentSource(request));
|
||||
};
|
||||
state.on("dispose", () => { sourceDisposed = true; sourceGeneration++; });
|
||||
let selectedSourceKey = null;
|
||||
const sourceKey = (parsed) => parsed.gistId && !parsed.repo
|
||||
? "gist:" + parsed.gistId.toLowerCase()
|
||||
: (parsed.owner + "/" + parsed.repo).toLowerCase() + (parsed.pullRequestId ? "/pull/" + parsed.pullRequestId : "");
|
||||
const draft = formDraft(state, ["sourceUrl", "terms", "repoId", "pullRequestId", "gistId", "source", "options", "conference", "githubConnection"], "anonymize:" + (location.path?.() || ""));
|
||||
state.discardDraft = async () => {
|
||||
draft.discard();
|
||||
state.workspaceOpen = !!state.sourceUrl;
|
||||
state.error = null;
|
||||
if (state.sourceUrl) await state.urlSelected(true);
|
||||
else {
|
||||
sourceGeneration++;
|
||||
state.sourceLoading = false;
|
||||
state.detectedType = null;
|
||||
state.details = null;
|
||||
state.sourceUrlError = "";
|
||||
scheduleReadmePreview.cancel();
|
||||
refreshPrPreview.cancel();
|
||||
refreshGistPreview.cancel();
|
||||
state.previewStatus = "idle";
|
||||
}
|
||||
};
|
||||
state.githubConnection = undefined;
|
||||
state.githubConnections = null;
|
||||
state.gistOAuthRequired = false;
|
||||
@@ -985,6 +1158,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
data.options && data.options.expirationDate
|
||||
? new Date(data.options.expirationDate)
|
||||
: defaultExpirationDate();
|
||||
if (!params.repoId && !params.pullRequestId && !params.gistId && !state.anonymize?.terms?.dirty) state.terms = state.defaultTerms;
|
||||
if (cb) cb();
|
||||
});
|
||||
}
|
||||
@@ -1039,7 +1213,12 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
let saved;
|
||||
try { saved = JSON.parse(sessionStorage.getItem("github-access-draft") || "null"); }
|
||||
catch (_) { return false; }
|
||||
if (!saved || saved.path !== location.path() || Date.now() - saved.savedAt >= 30 * 60000) return false;
|
||||
if (!saved || saved.path !== location.path() || Date.now() - saved.savedAt >= 30 * 60000) {
|
||||
if (!draft.restore()) return false;
|
||||
if (state.sourceUrl) await refreshGitHubAccess();
|
||||
return true;
|
||||
}
|
||||
draft.restore();
|
||||
for (const key of ["sourceUrl", "terms", "repoId", "pullRequestId", "gistId", "source", "options", "conference", "githubConnection"]) {
|
||||
if (Object.prototype.hasOwnProperty.call(saved.draft, key)) state[key] = saved.draft[key];
|
||||
}
|
||||
@@ -1079,6 +1258,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
if (res.data.options.expirationDate) {
|
||||
state.options.expirationDate = new Date(res.data.options.expirationDate);
|
||||
}
|
||||
selectedSourceKey = sourceKey(parseGithubUrl(state.sourceUrl));
|
||||
if (await restoreGitHubDraft()) return;
|
||||
await Promise.all([getRepoDetails(), getReadme()]);
|
||||
anonymizeReadme();
|
||||
@@ -1104,19 +1284,10 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
if (res.data.options.expirationDate) {
|
||||
state.options.expirationDate = new Date(res.data.options.expirationDate);
|
||||
}
|
||||
selectedSourceKey = sourceKey(parseGithubUrl(state.sourceUrl));
|
||||
if (await restoreGitHubDraft()) return;
|
||||
try {
|
||||
state.details = (await http.get(`/api/pr/${res.data.source.repositoryFullName}/${res.data.source.pullRequestId}`, { params: { connection: state.githubConnection } })).data;
|
||||
} catch (error) {
|
||||
const code = error && error.data && error.data.error;
|
||||
if (code) {
|
||||
translate("ERRORS." + code).then((translation) => {
|
||||
state.addToast({ title: "Error", date: new Date(), body: translation });
|
||||
state.error = translation;
|
||||
}, console.error);
|
||||
displayErrorMessage(code);
|
||||
}
|
||||
}
|
||||
try { await getPrDetails(); }
|
||||
catch (_) { /* The source handler displays the error. */ }
|
||||
|
||||
},
|
||||
() => { location.url("/404"); }
|
||||
@@ -1138,6 +1309,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
if (res.data.options.expirationDate) {
|
||||
state.options.expirationDate = new Date(res.data.options.expirationDate);
|
||||
}
|
||||
selectedSourceKey = sourceKey(parseGithubUrl(state.sourceUrl));
|
||||
if (await restoreGitHubDraft()) return;
|
||||
await getGistDetails();
|
||||
|
||||
@@ -1153,36 +1325,56 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
if (!params.repoId && !params.pullRequestId && !params.gistId && state.githubConnection === undefined
|
||||
&& res.data.appEnabled && res.data.appConnected) {
|
||||
state.githubConnection = "github-app";
|
||||
if (state.sourceUrl && state.workspaceOpen) refreshGitHubAccess();
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
// URL change handler - auto-detect type
|
||||
// Keep the same input focused until a complete GitHub URL can be loaded.
|
||||
state.urlSelected = async (preserveDraft = false) => {
|
||||
const generation = ++sourceGeneration;
|
||||
let o;
|
||||
try { o = parseGithubUrl(state.sourceUrl.trim()); }
|
||||
catch (_) {
|
||||
state.sourceLoading = false;
|
||||
scheduleReadmePreview.cancel();
|
||||
refreshPrPreview.cancel();
|
||||
refreshGistPreview.cancel();
|
||||
state.previewStatus = "idle";
|
||||
state.sourceUrlError = state.sourceUrl ? "Please provide a valid GitHub URL." : "";
|
||||
setValidity("sourceUrl", "github", false);
|
||||
return;
|
||||
}
|
||||
const key = sourceKey(o);
|
||||
const preserveSelection = preserveDraft || selectedSourceKey === key;
|
||||
selectedSourceKey = key;
|
||||
state.sourceUrlError = "";
|
||||
setValidity("sourceUrl", "github", true);
|
||||
const transferFocus = document.activeElement?.id === "sourceUrl-landing";
|
||||
state.workspaceOpen = true;
|
||||
if (transferFocus) timeout(() => document.getElementById("sourceUrl")?.focus(), 0);
|
||||
state.reconnectingSource = false;
|
||||
state.reconnectRepositoryId = undefined;
|
||||
if (!preserveDraft) state.terms = state.defaultTerms;
|
||||
if (!preserveDraft && !state.isUpdate) {
|
||||
if (!preserveSelection && !state.isUpdate) {
|
||||
state.repoId = "";
|
||||
state.pullRequestId = "";
|
||||
state.gistId = "";
|
||||
}
|
||||
state.details = null;
|
||||
state.branches = [];
|
||||
if (!preserveDraft) state.source = { type: "GitHubStream", branch: "", commit: "" };
|
||||
if (!preserveSelection) state.source = { type: "GitHubStream", branch: "", commit: "" };
|
||||
scheduleReadmePreview.cancel();
|
||||
refreshPrPreview.cancel();
|
||||
refreshGistPreview.cancel();
|
||||
_prAnonCache.clear();
|
||||
_gistAnonCache.clear();
|
||||
state.previewStatus = "idle";
|
||||
state.anonymize_readme = "";
|
||||
state.readme = "";
|
||||
state.html_readme = "";
|
||||
state.detectedType = null;
|
||||
state.gistOAuthRequired = false;
|
||||
|
||||
let o;
|
||||
try {
|
||||
o = parseGithubUrl(state.sourceUrl);
|
||||
} catch (error) {
|
||||
setValidity("sourceUrl", "github", false);
|
||||
return;
|
||||
}
|
||||
setValidity("sourceUrl", "github", true);
|
||||
state._preservingDraft = preserveSelection;
|
||||
try {
|
||||
if (o.gistId && !o.repo) {
|
||||
state.detectedType = "gist";
|
||||
@@ -1197,10 +1389,8 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
await Promise.all([getRepoDetails(), getReadme()]);
|
||||
anonymizeReadme();
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (_) { /* Source errors are displayed by the request handlers. */ }
|
||||
finally { if (generation === sourceGeneration) state._preservingDraft = false; }
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
};
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
@@ -1231,10 +1421,12 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
|
||||
state.getBranches = async (force) => {
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const request = beginSourceRequest();
|
||||
try {
|
||||
const branches = await http.get(`/api/repo/${o.owner}/${o.repo}/branches`, {
|
||||
params: { reconnect: state.reconnectingSource ? "1" : undefined, anonymizedRepoId: state.isUpdate && params.repoId && parseRepoFullName(state.sourceUrl) === state._originalFullName ? params.repoId : undefined, connection: state.githubConnection, force: force === true || state.reconnectingSource ? "1" : "0", repositoryID: sourceRepositoryID() },
|
||||
});
|
||||
if (!isCurrentSource(request)) return;
|
||||
state.branches = branches.data;
|
||||
state.sourceUnreachable = false;
|
||||
if (!state.source.branch) {
|
||||
@@ -1259,6 +1451,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
await getReadme(force);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrentSource(request)) return;
|
||||
state.branches = [];
|
||||
state.sourceUnreachable = error && (error.status === 404 || (error.data && error.data.error === "repo_not_found"));
|
||||
const code = (error && error.data && error.data.error) || (error && error.status === 404 ? "repo_not_found" : "unknown_error");
|
||||
@@ -1270,12 +1463,13 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
if (typeof setValidity === "function") {
|
||||
setValidity("sourceUrl", "missing", false);
|
||||
}
|
||||
}
|
||||
} finally { finishSourceRequest(request); }
|
||||
|
||||
};
|
||||
|
||||
async function getRepoDetails() {
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const request = beginSourceRequest();
|
||||
try {
|
||||
resetValidity();
|
||||
// force=1 so newly enabled features (e.g. GitHub Pages — see
|
||||
@@ -1284,6 +1478,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
const res = await http.get(`/api/repo/${o.owner}/${o.repo}/`, {
|
||||
params: { reconnect: state.reconnectingSource ? "1" : undefined, anonymizedRepoId: state.isUpdate && params.repoId && parseRepoFullName(state.sourceUrl) === state._originalFullName ? params.repoId : undefined, connection: state.githubConnection, repositoryID: sourceRepositoryID(), force: "1" },
|
||||
});
|
||||
if (!isCurrentSource(request)) return;
|
||||
state.details = res.data;
|
||||
if (state.reconnectingSource) state.reconnectRepositoryId = state.details.externalId;
|
||||
if (state.details && state.details.id) {
|
||||
@@ -1294,6 +1489,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
await state.getBranches(state.reconnectingSource);
|
||||
} catch (error) {
|
||||
if (!isCurrentSource(request)) return;
|
||||
if (error.data) {
|
||||
translate("ERRORS." + error.data.error).then((translation) => {
|
||||
state.addToast({ title: "Error", date: new Date(), body: translation });
|
||||
@@ -1303,20 +1499,21 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
setValidity("sourceUrl", "missing", false);
|
||||
throw error;
|
||||
}
|
||||
} finally { finishSourceRequest(request); }
|
||||
}
|
||||
|
||||
async function getReadme(force) {
|
||||
if (state.readme && !force) return state.readme;
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const request = beginSourceRequest();
|
||||
try {
|
||||
const res = await http.get(`/api/repo/${o.owner}/${o.repo}/readme`, {
|
||||
params: { reconnect: state.reconnectingSource ? "1" : undefined, anonymizedRepoId: state.isUpdate && params.repoId && parseRepoFullName(state.sourceUrl) === state._originalFullName ? params.repoId : undefined, connection: state.githubConnection, force: force === true ? "1" : "0", branch: state.source.branch, repositoryID: sourceRepositoryID() },
|
||||
});
|
||||
state.readme = res.data;
|
||||
if (isCurrentSource(request, true)) state.readme = res.data;
|
||||
} catch (error) {
|
||||
state.readme = "";
|
||||
}
|
||||
if (isCurrentSource(request, true)) state.readme = "";
|
||||
} finally { finishSourceRequest(request); }
|
||||
}
|
||||
|
||||
// Both anonymizeReadme() and anonymizePrContent() used to reimplement
|
||||
@@ -1344,36 +1541,51 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
return opts;
|
||||
}
|
||||
|
||||
// Single-flight + debounced wrapper. Returns a promise that resolves
|
||||
// with the latest server result; intermediate calls are coalesced.
|
||||
function makePreviewBatcher(buildBody, applyResult) {
|
||||
// Invalidate as soon as an option changes, before the debounce expires.
|
||||
function makePreviewBatcher(kind, buildBody, applyResult) {
|
||||
let pendingTimer = null;
|
||||
let inflightToken = 0;
|
||||
return function schedule() {
|
||||
let generation = 0;
|
||||
const schedule = () => {
|
||||
if (state.detectedType !== kind) return;
|
||||
if (pendingTimer) timeout.cancel(pendingTimer);
|
||||
const current = ++generation;
|
||||
state.previewStatus = "loading";
|
||||
pendingTimer = timeout(() => {
|
||||
pendingTimer = null;
|
||||
const myToken = ++inflightToken;
|
||||
const body = buildBody();
|
||||
if (!body) return;
|
||||
http.post("/api/anonymize-preview", body).then(
|
||||
(res) => {
|
||||
if (myToken !== inflightToken) return; // stale
|
||||
applyResult(res.data);
|
||||
},
|
||||
() => { /* ignore preview errors; no UI feedback needed */ }
|
||||
);
|
||||
if (!body) { state.previewStatus = "idle"; return; }
|
||||
http.post("/api/anonymize-preview", body).then(res => {
|
||||
if (current !== generation) return;
|
||||
applyResult(res.data, body);
|
||||
state.previewStatus = "ready";
|
||||
}).catch(() => {
|
||||
if (current === generation) state.previewStatus = "error";
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
schedule.cancel = () => {
|
||||
generation++;
|
||||
if (pendingTimer) timeout.cancel(pendingTimer);
|
||||
pendingTimer = null;
|
||||
};
|
||||
state.on("dispose", schedule.cancel);
|
||||
return schedule;
|
||||
}
|
||||
state.retryPreview = () => {
|
||||
if (state.detectedType === "repo") scheduleReadmePreview();
|
||||
if (state.detectedType === "pr") refreshPrPreview();
|
||||
if (state.detectedType === "gist") refreshGistPreview();
|
||||
};
|
||||
|
||||
const scheduleReadmePreview = makePreviewBatcher(
|
||||
"repo",
|
||||
() => {
|
||||
if (!state.readme) return null;
|
||||
return { content: state.readme, options: previewOptions() };
|
||||
},
|
||||
(data) => {
|
||||
state.anonymize_readme = data.content || "";
|
||||
if (typeof data?.content !== "string") throw Error("Invalid preview response");
|
||||
state.anonymize_readme = data.content;
|
||||
let baseUrl = "";
|
||||
try {
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
@@ -1406,14 +1618,17 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
// ========== PR LOGIC ==========
|
||||
async function getPrDetails() {
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const request = beginSourceRequest();
|
||||
try {
|
||||
resetValidity();
|
||||
const res = await http.get(`/api/pr/${o.owner}/${o.repo}/${o.pullRequestId}`, { params: { connection: state.githubConnection } });
|
||||
if (!isCurrentSource(request)) return;
|
||||
state.details = res.data;
|
||||
if (!state.pullRequestId) {
|
||||
state.pullRequestId = o.repo + "-PR" + o.pullRequestId + "-" + generateRandomId(4);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrentSource(request)) return;
|
||||
if (error.data) {
|
||||
translate("ERRORS." + error.data.error).then((translation) => {
|
||||
state.addToast({ title: "Error", date: new Date(), body: translation });
|
||||
@@ -1423,16 +1638,11 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
setValidity("sourceUrl", "missing", false);
|
||||
throw error;
|
||||
}
|
||||
} finally { finishSourceRequest(request); }
|
||||
}
|
||||
|
||||
// Angular templates evaluate this synchronously, so we keep a
|
||||
// {original -> anonymized} cache populated by a debounced batch call to
|
||||
// /api/anonymize-preview whenever the PR details, terms, or options
|
||||
// change. anonymizePrContent() returns the cached value if known and
|
||||
// falls back to the original until the next cycle resolves.
|
||||
// Original text is never substituted for a pending or failed preview.
|
||||
const _prAnonCache = reactive(new Map());
|
||||
let _prSeenContents = new Set();
|
||||
|
||||
function collectPrContents() {
|
||||
const out = new Set();
|
||||
@@ -1451,16 +1661,16 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
|
||||
const refreshPrPreview = makePreviewBatcher(
|
||||
"pr",
|
||||
() => {
|
||||
const seen = collectPrContents();
|
||||
_prSeenContents = seen;
|
||||
const list = Array.from(seen);
|
||||
if (list.length === 0) return null;
|
||||
return { contents: list, options: previewOptions() };
|
||||
},
|
||||
(data) => {
|
||||
if (!data || !Array.isArray(data.contents)) return;
|
||||
const seen = Array.from(_prSeenContents);
|
||||
(data, body) => {
|
||||
if (!Array.isArray(data?.contents) || data.contents.length !== body.contents.length || data.contents.some(value => typeof value !== "string")) throw Error("Invalid preview response");
|
||||
const seen = body.contents;
|
||||
const next = new Map();
|
||||
for (let i = 0; i < seen.length && i < data.contents.length; i++) {
|
||||
next.set(seen[i], data.contents[i]);
|
||||
@@ -1473,15 +1683,13 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
state.anonymizePrContent = function (content) {
|
||||
if (!content) return content;
|
||||
if (_prAnonCache.has(content)) return _prAnonCache.get(content);
|
||||
if (!_prSeenContents.has(content)) {
|
||||
refreshPrPreview();
|
||||
}
|
||||
return content;
|
||||
return "";
|
||||
};
|
||||
|
||||
// ========== GIST LOGIC ==========
|
||||
async function getGistDetails() {
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const request = beginSourceRequest();
|
||||
try {
|
||||
resetValidity();
|
||||
state.gistOAuthRequired = false;
|
||||
@@ -1491,11 +1699,13 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
return;
|
||||
}
|
||||
const res = await http.get(`/api/gist/source/${o.gistId}`);
|
||||
if (!isCurrentSource(request)) return;
|
||||
state.details = res.data;
|
||||
if (!state.gistId) {
|
||||
state.gistId = "gist-" + o.gistId.substring(0, 6) + "-" + generateRandomId(4);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrentSource(request)) return;
|
||||
if (error.data?.error === "github_oauth_required") {
|
||||
state.gistOAuthRequired = true;
|
||||
setValidity("sourceUrl", "missing", false);
|
||||
@@ -1510,11 +1720,10 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
setValidity("sourceUrl", "missing", false);
|
||||
throw error;
|
||||
}
|
||||
} finally { finishSourceRequest(request); }
|
||||
}
|
||||
|
||||
const _gistAnonCache = reactive(new Map());
|
||||
let _gistSeenContents = new Set();
|
||||
|
||||
function collectGistContents() {
|
||||
const out = new Set();
|
||||
@@ -1536,16 +1745,16 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
}
|
||||
|
||||
const refreshGistPreview = makePreviewBatcher(
|
||||
"gist",
|
||||
() => {
|
||||
const seen = collectGistContents();
|
||||
_gistSeenContents = seen;
|
||||
const list = Array.from(seen);
|
||||
if (list.length === 0) return null;
|
||||
return { contents: list, options: previewOptions() };
|
||||
},
|
||||
(data) => {
|
||||
if (!data || !Array.isArray(data.contents)) return;
|
||||
const seen = Array.from(_gistSeenContents);
|
||||
(data, body) => {
|
||||
if (!Array.isArray(data?.contents) || data.contents.length !== body.contents.length || data.contents.some(value => typeof value !== "string")) throw Error("Invalid preview response");
|
||||
const seen = body.contents;
|
||||
const next = new Map();
|
||||
for (let i = 0; i < seen.length && i < data.contents.length; i++) {
|
||||
next.set(seen[i], data.contents[i]);
|
||||
@@ -1559,10 +1768,7 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
state.anonymizeGistContent = function (content) {
|
||||
if (!content) return content;
|
||||
if (_gistAnonCache.has(content)) return _gistAnonCache.get(content);
|
||||
if (!_gistSeenContents.has(content)) {
|
||||
refreshGistPreview();
|
||||
}
|
||||
return content;
|
||||
return "";
|
||||
};
|
||||
|
||||
// Precomputed file objects for the preview pane so <gist-file>'s
|
||||
@@ -1731,9 +1937,15 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
|
||||
// Submit: repo
|
||||
state.anonymizeRepo = (event) => {
|
||||
if (state.sourceLoading) { state.error = "Wait for the source to finish loading before saving."; return; }
|
||||
if (state.reconnectingSource && !state.reconnectRepositoryId) return;
|
||||
if (expirationDateInvalid()) return;
|
||||
event.target.disabled = true;
|
||||
if (state.anonymize?.invalid || state.sourceUrlError) {
|
||||
state.error = "Please correct the highlighted fields before saving.";
|
||||
return;
|
||||
}
|
||||
const button = event.currentTarget || event.target.closest?.("button") || event.target;
|
||||
button.disabled = true;
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const payload = {
|
||||
repoId: state.repoId,
|
||||
@@ -1750,20 +1962,26 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
resetValidity();
|
||||
const url = state.isUpdate ? "/api/repo/" + state.repoId : "/api/repo/";
|
||||
http.post(url, payload, { headers: { "Content-Type": "application/json" } }).then(
|
||||
() => { window.location.href = "/status/" + state.repoId; },
|
||||
() => { draft.clear(); window.location.href = "/status/" + state.repoId; },
|
||||
(error) => {
|
||||
if (error.data) {
|
||||
translate("ERRORS." + error.data.error).then((t) => { state.error = t; }, console.error);
|
||||
displayErrorMessage(error.data.error);
|
||||
}
|
||||
}
|
||||
).finally(() => { event.target.disabled = false; });
|
||||
).finally(() => { button.disabled = false; });
|
||||
};
|
||||
|
||||
// Submit: Gist
|
||||
state.anonymizeGist = (event) => {
|
||||
if (state.sourceLoading) { state.error = "Wait for the source to finish loading before saving."; return; }
|
||||
if (expirationDateInvalid()) return;
|
||||
event.target.disabled = true;
|
||||
if (state.anonymize?.invalid || state.sourceUrlError) {
|
||||
state.error = "Please correct the highlighted fields before saving.";
|
||||
return;
|
||||
}
|
||||
const button = event.currentTarget || event.target.closest?.("button") || event.target;
|
||||
button.disabled = true;
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const payload = {
|
||||
gistId: state.gistId,
|
||||
@@ -1775,20 +1993,26 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
resetValidity();
|
||||
const url = state.isUpdate ? "/api/gist/" + state.gistId : "/api/gist/";
|
||||
http.post(url, payload, { headers: { "Content-Type": "application/json" } }).then(
|
||||
() => { window.location.href = "/gist/" + state.gistId; },
|
||||
() => { draft.clear(); window.location.href = "/gist/" + state.gistId; },
|
||||
(error) => {
|
||||
if (error.data) {
|
||||
translate("ERRORS." + error.data.error).then((t) => { state.error = t; }, console.error);
|
||||
displayErrorMessage(error.data.error);
|
||||
}
|
||||
}
|
||||
).finally(() => { event.target.disabled = false; });
|
||||
).finally(() => { button.disabled = false; });
|
||||
};
|
||||
|
||||
// Submit: PR
|
||||
state.anonymizePullRequest = (event) => {
|
||||
if (state.sourceLoading) { state.error = "Wait for the source to finish loading before saving."; return; }
|
||||
if (expirationDateInvalid()) return;
|
||||
event.target.disabled = true;
|
||||
if (state.anonymize?.invalid || state.sourceUrlError) {
|
||||
state.error = "Please correct the highlighted fields before saving.";
|
||||
return;
|
||||
}
|
||||
const button = event.currentTarget || event.target.closest?.("button") || event.target;
|
||||
button.disabled = true;
|
||||
const o = parseGithubUrl(state.sourceUrl);
|
||||
const payload = {
|
||||
pullRequestId: state.pullRequestId,
|
||||
@@ -1801,14 +2025,14 @@ export const anonymizeController = function (state, http, html, params, location
|
||||
resetValidity();
|
||||
const url = state.isUpdate ? "/api/pr/" + state.pullRequestId : "/api/pr/";
|
||||
http.post(url, payload, { headers: { "Content-Type": "application/json" } }).then(
|
||||
() => { window.location.href = "/pr/" + state.pullRequestId; },
|
||||
() => { draft.clear(); window.location.href = "/pr/" + state.pullRequestId; },
|
||||
(error) => {
|
||||
if (error.data) {
|
||||
translate("ERRORS." + error.data.error).then((t) => { state.error = t; }, console.error);
|
||||
displayErrorMessage(error.data.error);
|
||||
}
|
||||
}
|
||||
).finally(() => { event.target.disabled = false; });
|
||||
).finally(() => { button.disabled = false; });
|
||||
};
|
||||
|
||||
state.watch("conference", () => { getConference(); });
|
||||
@@ -2589,6 +2813,8 @@ export const conferencesController = function (state, http, location) {
|
||||
}
|
||||
|
||||
state.conferences = [];
|
||||
state.loading = true;
|
||||
state.error = "";
|
||||
state.search = "";
|
||||
|
||||
const conferencesPrefsKey = "conferences.filterPrefs";
|
||||
@@ -2624,11 +2850,13 @@ export const conferencesController = function (state, http, location) {
|
||||
);
|
||||
|
||||
state.removeConference = function (conf) {
|
||||
if (conf.removing) return;
|
||||
if (
|
||||
confirm(
|
||||
`Are you sure that you want to remove the conference ${conf.name}? All the repositories linked to this conference will expire.`
|
||||
)
|
||||
) {
|
||||
conf.removing = true;
|
||||
const toast = reactive({
|
||||
title: `Removing ${conf.name}...`,
|
||||
date: new Date(),
|
||||
@@ -2638,30 +2866,36 @@ export const conferencesController = function (state, http, location) {
|
||||
http.delete(`/api/conferences/${conf.conferenceID}`).then(() => {
|
||||
toast.title = `${conf.name} is removed.`;
|
||||
toast.body = `The conference ${conf.name} is removed.`;
|
||||
getConferences();
|
||||
});
|
||||
state.loadConferences();
|
||||
}).catch(() => {
|
||||
toast.title = "Unable to remove conference";
|
||||
toast.body = "Please try again. The conference has not been removed.";
|
||||
}).finally(() => { conf.removing = false; });
|
||||
}
|
||||
};
|
||||
|
||||
function getConferences() {
|
||||
http.get("/api/conferences/").then(
|
||||
state.loadConferences = () => {
|
||||
state.loading = true;
|
||||
state.error = "";
|
||||
return http.get("/api/conferences/").then(
|
||||
(res) => {
|
||||
state.conferences = res.data || [];
|
||||
},
|
||||
(err) => {
|
||||
console.error(err);
|
||||
state.error = "Unable to load your conferences. Please try again.";
|
||||
}
|
||||
);
|
||||
}
|
||||
getConferences();
|
||||
).finally(() => { state.loading = false; });
|
||||
};
|
||||
state.loadConferences();
|
||||
|
||||
state.conferenceFilter = (conference) => {
|
||||
if (state.filters.status[conference.status] == false) return false;
|
||||
|
||||
if (state.search.trim().length == 0) return true;
|
||||
const query = state.search.trim().toLowerCase();
|
||||
if (!query) return true;
|
||||
|
||||
if (conference.name.indexOf(state.search) > -1) return true;
|
||||
if (conference.conferenceID.indexOf(state.search) > -1) return true;
|
||||
if ((conference.name || "").toLowerCase().includes(query)) return true;
|
||||
if ((conference.conferenceID || "").toLowerCase().includes(query)) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -2679,12 +2913,14 @@ export const newConferenceController = function (state, http, location, params)
|
||||
|
||||
state.plans = [];
|
||||
state.editionMode = false;
|
||||
state.saving = false;
|
||||
state.error = "";
|
||||
|
||||
function getConference() {
|
||||
http
|
||||
.get("/api/conferences/" + params.conferenceId)
|
||||
.then((res) => {
|
||||
state.options = res.data;
|
||||
state.options = { ...state.options, ...res.data, billing: res.data.billing || {} };
|
||||
state.options.startDate = new Date(state.options.startDate);
|
||||
state.options.endDate = new Date(state.options.endDate);
|
||||
});
|
||||
@@ -2710,6 +2946,7 @@ export const newConferenceController = function (state, http, location, params)
|
||||
const end = new Date(start);
|
||||
end.setMonth(start.getMonth() + 7, 0);
|
||||
state.options = {
|
||||
billing: {},
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
plan: {
|
||||
@@ -2777,39 +3014,44 @@ export const newConferenceController = function (state, http, location, params)
|
||||
}
|
||||
|
||||
state.submit = function () {
|
||||
if (state.saving) return;
|
||||
state.saving = true;
|
||||
state.error = "";
|
||||
const submitted = JSON.parse(JSON.stringify(state.options));
|
||||
const toast = reactive({
|
||||
title: `Creating ${state.options.name}...`,
|
||||
title: `Creating ${submitted.name}...`,
|
||||
date: new Date(),
|
||||
body: `The conference ${state.options.conferenceID} is in creation.`,
|
||||
body: `The conference ${submitted.conferenceID} is in creation.`,
|
||||
});
|
||||
if (state.editionMode) {
|
||||
toast.title = `Updating ${state.options.name}...`;
|
||||
toast.body = `The conference '${state.options.conferenceID}' is updating.`;
|
||||
toast.title = `Updating ${submitted.name}...`;
|
||||
toast.body = `The conference '${submitted.conferenceID}' is updating.`;
|
||||
}
|
||||
state.addToast(toast);
|
||||
resetValidity();
|
||||
http
|
||||
.post(
|
||||
"/api/conferences/" +
|
||||
(state.editionMode ? state.options.conferenceID : ""),
|
||||
state.options
|
||||
(state.editionMode ? submitted.conferenceID : ""),
|
||||
submitted
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
if (!state.editionMode) {
|
||||
toast.title = `${state.options.name} created`;
|
||||
toast.body = `The conference '${state.options.conferenceID}' is created.`;
|
||||
toast.title = `${submitted.name} created`;
|
||||
toast.body = `The conference '${submitted.conferenceID}' is created.`;
|
||||
} else {
|
||||
toast.title = `${state.options.name} updated`;
|
||||
toast.body = `The conference '${state.options.conferenceID}' is updated.`;
|
||||
toast.title = `${submitted.name} updated`;
|
||||
toast.body = `The conference '${submitted.conferenceID}' is updated.`;
|
||||
}
|
||||
location.url("/conference/" + state.options.conferenceID);
|
||||
location.url("/conference/" + submitted.conferenceID);
|
||||
},
|
||||
(error) => {
|
||||
displayErrorMessage(error.data.error);
|
||||
displayErrorMessage(error.data?.error);
|
||||
state.error = "Unable to save the conference. Please correct any highlighted fields and try again.";
|
||||
state.removeToast(toast);
|
||||
}
|
||||
);
|
||||
).finally(() => { state.saving = false; });
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2833,10 +3075,11 @@ export const conferenceController = function (state, http, location, params) {
|
||||
state.repoFiler = (repo) => {
|
||||
if (state.filters.status[repo.status] == false) return false;
|
||||
|
||||
if (state.search.trim().length == 0) return true;
|
||||
const query = state.search.trim().toLowerCase();
|
||||
if (!query) return true;
|
||||
|
||||
if (repo.source.fullName.indexOf(state.search) > -1) return true;
|
||||
if (repo.repoId.indexOf(state.search) > -1) return true;
|
||||
if ((repo.source?.fullName || "").toLowerCase().includes(query)) return true;
|
||||
if ((repo.repoId || "").toLowerCase().includes(query)) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user