Skip to content

Instantly share code, notes, and snippets.

@parthi2929
Created January 15, 2026 19:45
Show Gist options
  • Select an option

  • Save parthi2929/32726fb80643850ef811d2cfc170f393 to your computer and use it in GitHub Desktop.

Select an option

Save parthi2929/32726fb80643850ef811d2cfc170f393 to your computer and use it in GitHub Desktop.
(function () {
if (window.GizaGitlabDrawioPluginLoaded) {
return;
}
window.GizaGitlabDrawioPluginLoaded = true;
var PAT_STORAGE_KEY = "giza.gitlab.pat";
var BASE_URL_STORAGE_KEY = "giza.gitlab.baseUrl";
var DEFAULT_GITLAB_BASE_URL = "https://gitlab.com";
function loadScriptOnce(id, src) {
return new Promise(function (resolve, reject) {
if (document.getElementById(id)) {
resolve();
return;
}
var script = document.createElement("script");
script.id = id;
script.src = src;
script.async = true;
script.onload = function () {
resolve();
};
script.onerror = function () {
reject(new Error("Failed to load script: " + src));
};
document.head.appendChild(script);
});
}
function loadStyleOnce(id, href) {
if (document.getElementById(id)) {
return;
}
var link = document.createElement("link");
link.id = id;
link.rel = "stylesheet";
link.href = href;
document.head.appendChild(link);
}
function ensureRenderDeps() {
var tasks = [
loadScriptOnce(
"giza-markdown-it",
"https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"
),
loadScriptOnce(
"giza-markdown-it-katex",
"https://cdn.jsdelivr.net/npm/markdown-it-katex@2.0.3/dist/markdown-it-katex.min.js"
)
];
loadStyleOnce(
"giza-katex-css",
"https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"
);
return Promise.all(tasks);
}
function normalizeGitlabMath(markdown) {
if (!markdown) {
return "";
}
return markdown
.replace(/\\\[/g, "$$")
.replace(/\\\]/g, "$$")
.replace(/\\\(/g, "$")
.replace(/\\\)/g, "$");
}
function renderMarkdownToHtml(markdown) {
return ensureRenderDeps().then(function () {
if (!window.gizaMarkdownIt) {
window.gizaMarkdownIt = window
.markdownit({
html: true,
linkify: true,
breaks: true
})
.use(window.markdownitKatex);
}
var normalized = normalizeGitlabMath(markdown);
return window.gizaMarkdownIt.render(normalized);
});
}
function getStoredToken() {
try {
return localStorage.getItem(PAT_STORAGE_KEY) || "";
} catch (error) {
return "";
}
}
function setStoredToken(token) {
try {
localStorage.setItem(PAT_STORAGE_KEY, token);
} catch (error) {
// Ignore storage errors.
}
}
function getStoredBaseUrl() {
try {
return localStorage.getItem(BASE_URL_STORAGE_KEY) || "";
} catch (error) {
return "";
}
}
function setStoredBaseUrl(baseUrl) {
try {
localStorage.setItem(BASE_URL_STORAGE_KEY, baseUrl);
} catch (error) {
// Ignore storage errors.
}
}
function showStatus(el, message, isError) {
el.textContent = message;
el.style.color = isError ? "#a00" : "#0a6";
}
function clearStatus(el) {
el.textContent = "";
}
function createDialog(ui, options) {
var container = document.createElement("div");
container.style.padding = "12px";
container.style.maxWidth = "520px";
var title = document.createElement("div");
title.textContent = options.title || "";
title.style.fontWeight = "bold";
title.style.marginBottom = "10px";
var body = document.createElement("div");
body.style.marginBottom = "12px";
var footer = document.createElement("div");
footer.style.display = "flex";
footer.style.justifyContent = "flex-end";
footer.style.gap = "8px";
container.appendChild(title);
container.appendChild(body);
container.appendChild(footer);
if (options.buildBody) {
options.buildBody(body, footer);
}
ui.showDialog(container, options.width || 420, options.height || 260, true, true);
return function closeDialog() {
ui.hideDialog();
};
}
function testToken(baseUrl, token) {
return fetch(baseUrl + "/api/v4/user", {
method: "GET",
headers: {
"PRIVATE-TOKEN": token
}
}).then(function (response) {
if (!response.ok) {
var error = new Error("Token validation failed");
error.status = response.status;
throw error;
}
return response.json();
});
}
function parseGitlabNoteUrl(url) {
var parsed;
try {
parsed = new URL(url);
} catch (error) {
return null;
}
var noteMatch = parsed.hash.match(/note_(\d+)/);
if (!noteMatch) {
return null;
}
var noteId = noteMatch[1];
var pathParts = parsed.pathname.split("/").filter(Boolean);
var dashIndex = pathParts.indexOf("-");
if (dashIndex === -1 || dashIndex + 2 >= pathParts.length) {
return null;
}
var projectPath = pathParts.slice(0, dashIndex).join("/");
var type = pathParts[dashIndex + 1];
var iid = pathParts[dashIndex + 2];
if (!projectPath || !type || !iid) {
return null;
}
if (type !== "issues" && type !== "merge_requests") {
return null;
}
return {
baseUrl: parsed.origin,
projectPath: projectPath,
type: type,
iid: iid,
noteId: noteId
};
}
function fetchGitlabNote(noteInfo, token) {
var encodedProject = encodeURIComponent(noteInfo.projectPath);
var apiUrl =
noteInfo.baseUrl +
"/api/v4/projects/" +
encodedProject +
"/" +
noteInfo.type +
"/" +
noteInfo.iid +
"/notes/" +
noteInfo.noteId;
return fetch(apiUrl, {
method: "GET",
headers: {
"PRIVATE-TOKEN": token
}
}).then(function (response) {
if (!response.ok) {
var error = new Error("Failed to fetch note");
error.status = response.status;
throw error;
}
return response.json();
});
}
function getVisibleOrigin(graph) {
var view = graph.view;
var scale = view.scale;
var tr = view.translate;
return {
x: Math.max(20, (-tr.x + 20) / scale),
y: Math.max(20, (-tr.y + 20) / scale)
};
}
function getVertexBounds(graph, cell) {
var bounds = graph.getCellBounds(cell);
if (!bounds) {
return null;
}
return {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
};
}
function intersects(rectA, rectB) {
return !(
rectB.x > rectA.x + rectA.width ||
rectB.x + rectB.width < rectA.x ||
rectB.y > rectA.y + rectA.height ||
rectB.y + rectB.height < rectA.y
);
}
function findNonOverlappingPosition(graph, width, height) {
var parent = graph.getDefaultParent();
var vertices = graph.getModel().getChildVertices(parent);
var origin = getVisibleOrigin(graph);
var offsetX = 20;
var offsetY = 20;
var x = origin.x;
var y = origin.y;
var attempts = 0;
while (attempts < 30) {
var candidate = {
x: x,
y: y,
width: width,
height: height
};
var overlaps = false;
for (var i = 0; i < vertices.length; i += 1) {
var bounds = getVertexBounds(graph, vertices[i]);
if (bounds && intersects(candidate, bounds)) {
overlaps = true;
break;
}
}
if (!overlaps) {
return { x: x, y: y };
}
x += offsetX;
y = Math.max(20, y - offsetY);
attempts += 1;
}
return { x: origin.x, y: origin.y };
}
function insertNoteNode(graph, htmlContent) {
var parent = graph.getDefaultParent();
var width = 360;
var height = 240;
var position = findNonOverlappingPosition(graph, width, height);
var style = [
"shape=swimlane",
"startSize=28",
"horizontal=0",
"html=1",
"whiteSpace=wrap",
"align=left",
"verticalAlign=top",
"spacing=8",
"rounded=1"
].join(";");
var label =
"<div style=\"width:100%;height:100%;overflow:hidden;font-size:12px;line-height:1.45;\">" +
"<div style=\"font-weight:bold;margin-bottom:6px;\">GitLab Note</div>" +
"<div>" +
htmlContent +
"</div>" +
"</div>";
var cell;
graph.getModel().beginUpdate();
try {
cell = graph.insertVertex(
parent,
null,
label,
position.x,
position.y,
width,
height,
style
);
} finally {
graph.getModel().endUpdate();
}
if (cell) {
graph.setSelectionCell(cell);
graph.scrollCellToVisible(cell);
}
}
function openSettingsDialog(ui) {
var close = createDialog(ui, {
title: "GitLab Settings",
width: 440,
height: 260,
buildBody: function (body, footer) {
var label = document.createElement("div");
label.textContent = "Personal Access Token";
label.style.marginBottom = "6px";
var input = document.createElement("input");
input.type = "password";
input.placeholder = "glpat-...";
input.value = getStoredToken();
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.style.marginBottom = "8px";
var status = document.createElement("div");
status.style.minHeight = "18px";
status.style.fontSize = "12px";
var testButton = document.createElement("button");
testButton.textContent = "Test";
var saveButton = document.createElement("button");
saveButton.textContent = "Save";
saveButton.style.fontWeight = "bold";
var cancelButton = document.createElement("button");
cancelButton.textContent = "Cancel";
testButton.onclick = function () {
clearStatus(status);
var token = input.value.trim();
if (!token) {
showStatus(status, "Token is required.", true);
return;
}
var baseUrl = getStoredBaseUrl() || DEFAULT_GITLAB_BASE_URL;
testButton.disabled = true;
showStatus(status, "Testing token...", false);
testToken(baseUrl, token)
.then(function () {
showStatus(status, "Token is valid.", false);
})
.catch(function (error) {
var message =
error && error.status === 401
? "Token unauthorized."
: "Token test failed.";
showStatus(status, message, true);
})
.finally(function () {
testButton.disabled = false;
});
};
saveButton.onclick = function () {
clearStatus(status);
var token = input.value.trim();
if (!token) {
showStatus(status, "Token is required.", true);
return;
}
setStoredToken(token);
showStatus(status, "Token saved.", false);
setTimeout(function () {
close();
}, 400);
};
cancelButton.onclick = function () {
close();
};
body.appendChild(label);
body.appendChild(input);
body.appendChild(status);
footer.appendChild(cancelButton);
footer.appendChild(testButton);
footer.appendChild(saveButton);
}
});
}
function openAddNoteDialog(ui) {
var close = createDialog(ui, {
title: "Add a GitLab note",
width: 520,
height: 300,
buildBody: function (body, footer) {
var label = document.createElement("div");
label.textContent = "GitLab note link";
label.style.marginBottom = "6px";
var input = document.createElement("input");
input.type = "text";
input.placeholder = "https://gitlab.com/group/project/-/issues/1#note_123";
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.style.marginBottom = "8px";
var status = document.createElement("div");
status.style.minHeight = "18px";
status.style.fontSize = "12px";
var addButton = document.createElement("button");
addButton.textContent = "Add";
addButton.style.fontWeight = "bold";
var cancelButton = document.createElement("button");
cancelButton.textContent = "Cancel";
addButton.onclick = function () {
clearStatus(status);
var token = getStoredToken();
if (!token) {
showStatus(status, "Save a token in Settings first.", true);
return;
}
var urlValue = input.value.trim();
if (!urlValue) {
showStatus(status, "Note link is required.", true);
return;
}
var noteInfo = parseGitlabNoteUrl(urlValue);
if (!noteInfo) {
showStatus(status, "Invalid note link.", true);
return;
}
setStoredBaseUrl(noteInfo.baseUrl);
addButton.disabled = true;
showStatus(status, "Fetching note...", false);
fetchGitlabNote(noteInfo, token)
.then(function (note) {
var bodyText = note && note.body ? note.body : "";
return renderMarkdownToHtml(bodyText);
})
.then(function (html) {
insertNoteNode(ui.editor.graph, html);
close();
})
.catch(function (error) {
var message =
error && error.status === 401
? "Unauthorized. Check your token."
: "Failed to fetch note.";
showStatus(status, message, true);
})
.finally(function () {
addButton.disabled = false;
});
};
cancelButton.onclick = function () {
close();
};
body.appendChild(label);
body.appendChild(input);
body.appendChild(status);
footer.appendChild(cancelButton);
footer.appendChild(addButton);
}
});
}
function addMenu(ui) {
var menu = ui.menus.addMenu("gizaTools", function (menu, parent) {
var notesFlowItem = menu.addItem("Notes Flow", null, null, parent);
menu.addItem("Add a GitLab note", null, function () {
openAddNoteDialog(ui);
}, notesFlowItem);
menu.addSeparator(parent);
menu.addItem("Settings", null, function () {
openSettingsDialog(ui);
}, parent);
});
ui.menubar.addMenu(menu, "Giza Tools", "last");
}
if (typeof Draw !== "undefined" && Draw.loadPlugin) {
Draw.loadPlugin(function (ui) {
addMenu(ui);
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment