bpmn-js-markdown-documentation-panel
Version:
A comprehensive documentation management plugin for Camunda Modeler with markdown support, element linking, and coverage tracking
2,033 lines • 67.4 kB
JavaScript
import { marked } from "marked";
//#region ../../node_modules/.pnpm/bpmn-js@13.2.2/node_modules/bpmn-js/lib/util/ModelUtil.js
/**
* @typedef { import('../model/Types').Element } Element
* @typedef { import('../model/Types').ModdleElement } ModdleElement
*/
/**
* Is an element of the given BPMN type?
*
* @param {Element|ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
function is(element, type) {
var bo = getBusinessObject(element);
return bo && typeof bo.$instanceOf === "function" && bo.$instanceOf(type);
}
/**
* Return the business object for a given element.
*
* @param {Element|ModdleElement} element
*
* @return {ModdleElement}
*/
function getBusinessObject(element) {
return element && element.businessObject || element;
}
//#endregion
//#region src/extension/managers/AutocompleteManager.ts
var AutocompleteManager = class {
_callbacks;
_selectedIndex = -1;
constructor(options) {
this._callbacks = options.callbacks;
}
setupAutocompleteEventListeners() {
setTimeout(() => {
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
textarea.addEventListener("input", () => {
this.handleAutocomplete();
});
textarea.addEventListener("keydown", (event) => {
this._handleAutocompleteKeydown(event);
});
document.addEventListener("click", (event) => {
const dropdown = document.getElementById("autocomplete-dropdown");
const textarea$1 = document.getElementById("doc-textarea");
if (dropdown && !(event.target instanceof Node && dropdown.contains(event.target)) && event.target !== textarea$1) this.hideAutocomplete();
});
}, 100);
}
handleAutocomplete() {
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
const cursorPos = textarea.selectionStart;
const text = textarea.value;
let hashPos = -1;
for (let i = cursorPos - 1; i >= 0; i--) {
if (text[i] === "#") {
hashPos = i;
break;
}
if (text[i] === " " || text[i] === "\n" || text[i] === " ") break;
}
if (hashPos >= 0) {
if (hashPos > 0 && text[hashPos - 1] === "(") {
let foundClosingBracket = false;
for (let i = hashPos - 2; i >= 0; i--) {
if (text[i] === "]") {
foundClosingBracket = true;
break;
}
if (text[i] === "\n" || text[i] === "\r") break;
}
if (foundClosingBracket) {
const searchText = text.substring(hashPos + 1, cursorPos);
if (!searchText.includes(" ") && !searchText.includes("\n")) {
this._showAutocomplete(searchText, hashPos);
return;
}
}
}
}
this.hideAutocomplete();
}
hideAutocomplete() {
const dropdown = document.getElementById("autocomplete-dropdown");
if (!dropdown) return;
dropdown.classList.remove("visible");
this._selectedIndex = -1;
}
destroy() {
this.hideAutocomplete();
}
_showAutocomplete(searchText, hashPos) {
const dropdown = document.getElementById("autocomplete-dropdown");
const autocompleteList = document.getElementById("autocomplete-list");
const textarea = document.getElementById("doc-textarea");
if (!dropdown || !autocompleteList || !textarea) return;
const allElements = this._getAllElements();
const filteredElements = allElements.filter((element) => element.id.toLowerCase().includes(searchText.toLowerCase()) || element.name.toLowerCase().includes(searchText.toLowerCase()));
if (filteredElements.length === 0) {
this.hideAutocomplete();
return;
}
autocompleteList.innerHTML = "";
filteredElements.slice(0, 10).forEach((element) => {
const item = document.createElement("div");
item.className = "autocomplete-item";
item.innerHTML = `
<div class="autocomplete-item-id">${element.id}</div>
<div class="autocomplete-item-name">${element.name}</div>
<div class="autocomplete-item-type">${element.type}</div>
`;
item.addEventListener("click", () => {
this._selectAutocompleteItem(element.id, hashPos);
});
autocompleteList.appendChild(item);
});
this._positionAutocomplete(textarea, hashPos);
dropdown.classList.add("visible");
this._selectedIndex = 0;
this._updateAutocompleteSelection(Array.from(autocompleteList.children));
}
_positionAutocomplete(textarea, hashPos) {
const dropdown = document.getElementById("autocomplete-dropdown");
if (!dropdown) return;
const textareaRect = textarea.getBoundingClientRect();
const style = window.getComputedStyle(textarea);
const tempSpan = document.createElement("span");
tempSpan.style.visibility = "hidden";
tempSpan.style.position = "absolute";
tempSpan.style.top = "-9999px";
tempSpan.style.fontFamily = style.fontFamily;
tempSpan.style.fontSize = style.fontSize;
tempSpan.style.fontWeight = style.fontWeight;
tempSpan.style.letterSpacing = style.letterSpacing;
tempSpan.style.whiteSpace = "pre";
const textUpToHash = textarea.value.substring(0, hashPos);
const linesUpToHash = textUpToHash.split("\n");
const currentLine = linesUpToHash[linesUpToHash.length - 1];
tempSpan.textContent = currentLine;
this._callbacks.getCanvasContainer().appendChild(tempSpan);
this._callbacks.getCanvasContainer().removeChild(tempSpan);
const left = textareaRect.left + 10;
const top = textareaRect.top + 100;
dropdown.style.setProperty("left", `${left}px`, "important");
dropdown.style.setProperty("top", `${top}px`, "important");
dropdown.style.setProperty("position", "fixed", "important");
dropdown.style.setProperty("z-index", "10001", "important");
}
_getAllElements() {
const elements = [];
const seenIds = /* @__PURE__ */ new Set();
const allElements = this._callbacks.getAllElements();
allElements.forEach((element) => {
if (element.businessObject?.id) {
const bo = element.businessObject;
const elementId = bo.id;
if (seenIds.has(elementId)) return;
seenIds.add(elementId);
elements.push({
id: elementId,
name: bo.name || "Unnamed",
type: this._callbacks.getElementTypeName(element)
});
}
});
return elements.sort((a, b) => a.id.localeCompare(b.id));
}
_handleAutocompleteKeydown(event) {
const dropdown = document.getElementById("autocomplete-dropdown");
if (!dropdown || !dropdown.classList.contains("visible")) return;
const items = Array.from(dropdown.querySelectorAll(".autocomplete-item"));
if (event.key === "ArrowDown") {
event.preventDefault();
this._selectedIndex = Math.min(this._selectedIndex + 1, items.length - 1);
this._updateAutocompleteSelection(items);
} else if (event.key === "ArrowUp") {
event.preventDefault();
this._selectedIndex = Math.max(this._selectedIndex - 1, 0);
this._updateAutocompleteSelection(items);
} else if (event.key === "Enter") {
event.preventDefault();
if (this._selectedIndex >= 0 && items[this._selectedIndex]) {
const selectedId = items[this._selectedIndex].querySelector(".autocomplete-item-id")?.textContent;
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
const cursorPos = textarea.selectionStart;
const text = textarea.value;
let hashPos = -1;
for (let i = cursorPos - 1; i >= 0; i--) if (text[i] === "#") {
hashPos = i;
break;
}
if (hashPos >= 0 && selectedId) this._selectAutocompleteItem(selectedId, hashPos);
}
} else if (event.key === "Escape") {
event.preventDefault();
this.hideAutocomplete();
}
}
_updateAutocompleteSelection(items) {
const dropdown = document.getElementById("autocomplete-dropdown");
const autocompleteList = document.getElementById("autocomplete-list");
if (!dropdown || !autocompleteList) return;
Array.from(items).forEach((item, index) => {
if (index === this._selectedIndex) {
item.classList.add("selected");
const itemTop = item.offsetTop;
const itemBottom = itemTop + item.offsetHeight;
const dropdownTop = dropdown.scrollTop;
const dropdownBottom = dropdownTop + dropdown.clientHeight;
if (itemTop < dropdownTop) dropdown.scrollTop = itemTop;
else if (itemBottom > dropdownBottom) dropdown.scrollTop = itemBottom - dropdown.clientHeight;
} else item.classList.remove("selected");
});
}
_selectAutocompleteItem(elementId, hashPos) {
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
const text = textarea.value;
const cursorPos = textarea.selectionStart;
const beforeHash = text.substring(0, hashPos + 1);
const afterCursor = text.substring(cursorPos);
const newText = beforeHash + elementId + afterCursor;
textarea.value = newText;
const newCursorPos = hashPos + 1 + elementId.length;
textarea.setSelectionRange(newCursorPos, newCursorPos);
this.hideAutocomplete();
this._callbacks.updatePreview();
this._callbacks.saveDocumentationLive();
textarea.focus();
}
};
//#endregion
//#region src/extension/managers/ExportManager.ts
var ExportManager = class {
_elementRegistry;
_moddle;
_canvas;
constructor(elementRegistry, moddle, canvas) {
this._elementRegistry = elementRegistry;
this._moddle = moddle;
this._canvas = canvas;
}
setupExportEventListeners() {
setTimeout(() => {
document.getElementById("export-btn")?.addEventListener("click", () => {
this.handleExport();
});
}, 100);
}
handleExport() {
this.exportDocumentation().catch((error) => {
console.error("Export failed:", error);
this._showNotification("Export failed", "error");
});
}
/**
* Export documentation in HTML format
*/
async exportDocumentation() {
try {
const processInfo = this._getProcessInfo();
const elements = this._getAllElementsWithDocumentation();
const documentedElements = elements.filter((el) => el.hasDocumentation);
if (documentedElements.length === 0) {
this._showNotification("No documented elements found to export", "warning");
return;
}
const htmlContent = await this._generateHTMLExport(elements, processInfo);
const processName = processInfo.name || processInfo.id || "Process";
const filename = `${processName}_Documentation.html`;
this._downloadFile(htmlContent, filename, "text/html");
this._showNotification(`Documentation exported successfully (${documentedElements.length} elements)`, "success");
} catch (error) {
console.error("Export failed:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
this._showNotification(`Export failed: ${errorMessage}`, "error");
}
}
/**
* Get BPMN diagram as SVG
*/
async _getDiagramSVG() {
try {
const canvasContainer = this._canvas.getContainer();
const svgElement = canvasContainer.querySelector("svg");
if (svgElement) {
const svgCopy = svgElement.cloneNode(true);
const allElements = this._elementRegistry.getAll();
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
let maxX = Number.NEGATIVE_INFINITY;
let maxY = Number.NEGATIVE_INFINITY;
allElements.forEach((element) => {
if (element.x !== void 0 && element.y !== void 0 && element.width !== void 0 && element.height !== void 0) {
minX = Math.min(minX, element.x);
minY = Math.min(minY, element.y);
maxX = Math.max(maxX, element.x + element.width);
maxY = Math.max(maxY, element.y + element.height);
}
});
const padding = 50;
const diagramWidth = maxX - minX;
const diagramHeight = maxY - minY;
const viewBoxX = minX - padding;
const viewBoxY = minY - padding;
const viewBoxWidth = diagramWidth + padding * 2;
const viewBoxHeight = diagramHeight + padding * 2;
const viewBox = `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`;
svgCopy.setAttribute("viewBox", viewBox);
const aspectRatio = viewBoxWidth / viewBoxHeight;
let svgWidth = 800;
let svgHeight = 600;
if (aspectRatio > svgWidth / svgHeight) svgHeight = svgWidth / aspectRatio;
else svgWidth = svgHeight * aspectRatio;
svgCopy.setAttribute("width", Math.round(svgWidth).toString());
svgCopy.setAttribute("height", Math.round(svgHeight).toString());
const background = document.createElementNS("http://www.w3.org/2000/svg", "rect");
background.setAttribute("x", viewBoxX.toString());
background.setAttribute("y", viewBoxY.toString());
background.setAttribute("width", viewBoxWidth.toString());
background.setAttribute("height", viewBoxHeight.toString());
background.setAttribute("fill", "#ffffff");
svgCopy.insertBefore(background, svgCopy.firstChild);
return svgCopy.outerHTML;
}
return "";
} catch (error) {
console.error("Error getting diagram SVG:", error);
return "";
}
}
/**
* Get all elements with their documentation status
*/
_getAllElementsWithDocumentation() {
const elements = [];
const seenIds = /* @__PURE__ */ new Set();
const allElements = this._elementRegistry.getAll();
allElements.forEach((element) => {
if (element.businessObject?.id) {
const bo = element.businessObject;
const elementId = bo.id;
if (seenIds.has(elementId)) return;
seenIds.add(elementId);
const documentation = this._getElementDocumentation(element);
elements.push({
id: elementId,
name: bo.name || "Unnamed",
type: this._getElementTypeName(element),
hasDocumentation: !!documentation?.trim(),
documentation: documentation || "",
element
});
}
});
return elements.sort((a, b) => a.id.localeCompare(b.id));
}
/**
* Get documentation for a specific element
*/
_getElementDocumentation(element) {
if (!element || !element.businessObject) return "";
const bo = element.businessObject;
if (bo.documentation && bo.documentation.length > 0) return bo.documentation[0].text || "";
return "";
}
/**
* Get element type name for display
*/
_getElementTypeName(element) {
if (!element || !element.businessObject) return "Unknown";
const bo = element.businessObject;
const type = bo.$type || "";
if (type.includes(":")) {
const typeName = type.split(":")[1];
return typeName.replace(/([A-Z])/g, " $1").trim();
}
return type || "Unknown";
}
/**
* Generate HTML export content
*/
async _generateHTMLExport(elements, processInfo) {
const totalElements = elements.length;
const documentedCount = elements.filter((el) => el.hasDocumentation).length;
const undocumentedCount = totalElements - documentedCount;
const coveragePercentage = totalElements > 0 ? Math.round(documentedCount / totalElements * 100) : 0;
const processTitle = processInfo.name || processInfo.id || "BPMN Process";
const processDocumentation = processInfo.element ? this._getElementDocumentation(processInfo.element) : "";
const diagramSVG = await this._getDiagramSVG();
const tocItems = elements.map((el) => `<li><a href="#element-${el.id}" class="toc-link">${el.name} (${el.id})</a></li>`).join("");
const elementSections = elements.map((el) => {
const markdownContent = el.documentation || "";
const htmlContent = markdownContent ? marked(markdownContent) : "<p class='no-documentation'><em>No documentation available</em></p>";
return `
<div class="element-section" id="element-${el.id}">
<div class="element-header">
<div class="element-title-info">
<h2 class="element-title">${el.name}</h2>
<div class="element-meta">
<span class="element-id">${el.id}</span>
</div>
</div>
</div>
<div class="element-content">
${htmlContent}
</div>
</div>
`;
}).join("");
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${this._escapeHtml(processTitle)} - Documentation</title>
<style>
${this._generateStyles()}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1 class="main-title">${this._escapeHtml(processTitle)}</h1>
${processDocumentation ? `<p class="subtitle">${this._escapeHtml(processDocumentation)}</p>` : ""}
<div class="export-info">
<span class="export-date">Generated on ${(/* @__PURE__ */ new Date()).toLocaleDateString()}</span>
</div>
</header>
<div class="stats-section">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">${totalElements}</div>
<div class="stat-label">Total Elements</div>
</div>
<div class="stat-card documented">
<div class="stat-number">${documentedCount}</div>
<div class="stat-label">Documented</div>
</div>
<div class="stat-card undocumented">
<div class="stat-number">${undocumentedCount}</div>
<div class="stat-label">Undocumented</div>
</div>
<div class="stat-card coverage">
<div class="stat-number">${coveragePercentage}%</div>
<div class="stat-label">Coverage</div>
</div>
</div>
</div>
${diagramSVG ? `
<div class="diagram-section">
<h2 class="section-title">Process Diagram</h2>
<div class="diagram-container">
${diagramSVG}
</div>
</div>
` : ""}
<div class="toc-section">
<h2 class="section-title">Table of Contents</h2>
<div class="toc-container">
<ul class="toc-list">
${tocItems}
</ul>
</div>
</div>
<div class="documentation-section">
<h2 class="section-title">Element Documentation</h2>
${elementSections}
</div>
<div class="back-to-top">
<button onclick="window.scrollTo({top: 0, behavior: 'smooth'})" class="back-to-top-btn">
↑ Back to Top
</button>
</div>
</div>
<script>
// Add smooth scrolling for table of contents links
document.querySelectorAll('.toc-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
// Show/hide back to top button
window.addEventListener('scroll', function() {
const backToTop = document.querySelector('.back-to-top');
if (window.scrollY > 300) {
backToTop.style.display = 'block';
} else {
backToTop.style.display = 'none';
}
});
<\/script>
</body>
</html>`;
}
/**
* Generate CSS styles for the HTML export
*/
_generateStyles() {
return `
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #262c33;
background: #f8f9fa;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 20px auto;
padding: 0;
background: white;
border: 1px solid #dae2ec;
border-radius: 8px;
overflow: hidden;
}
.header {
background: white;
color: #262c33;
padding: 60px 40px;
text-align: center;
border-bottom: 1px solid #dae2ec;
}
.main-title {
font-size: 2.5em;
font-weight: 600;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.1em;
opacity: 0.9;
margin-bottom: 20px;
}
.export-date {
font-size: 0.9em;
opacity: 0.8;
background: #fafafa;
padding: 4px 12px;
border-radius: 4px;
display: inline-block;
border: 1px solid #dae2ec;
}
.stats-section {
padding: 40px;
background: #fafafa;
border-bottom: 1px solid #dae2ec;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-card {
background: white;
padding: 25px 20px;
border-radius: 4px;
text-align: center;
border: 1px solid #dae2ec;
}
.stat-number {
font-size: 2.2em;
font-weight: 600;
color: #262c33;
display: block;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.9em;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.diagram-section {
padding: 40px;
background: white;
border-bottom: 1px solid #dae2ec;
}
.section-title {
font-size: 1.8em;
color: #262c33;
margin-bottom: 25px;
display: inline-block;
}
.diagram-container {
background: #fafafa;
border-radius: 4px;
padding: 20px;
text-align: center;
border: 1px solid #dae2ec;
}
.diagram-container svg {
max-width: 100%;
height: auto;
border-radius: 4px;
}
.toc-section {
padding: 40px;
background: #fafafa;
border-bottom: 1px solid #dae2ec;
}
.toc-container {
background: white;
border-radius: 4px;
padding: 30px;
border: 1px solid #dae2ec;
}
.toc-list {
list-style: none;
columns: 2;
column-gap: 30px;
column-fill: balance;
}
.toc-list li {
break-inside: avoid;
margin-bottom: 8px;
position: relative;
}
.toc-link {
color: #262c33;
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
}
.toc-link:hover {
color: #666;
}
.documentation-section {
padding: 40px;
background: white;
}
.element-section {
margin-bottom: 30px;
background: white;
border-radius: 4px;
border: 1px solid #dae2ec;
overflow: hidden;
}
.element-header {
background: white;
color: #262c33;
padding: 20px 25px;
display: flex;
align-items: center;
gap: 15px;
border-bottom: 1px solid #dae2ec;
}
.element-title-info {
flex: 1;
}
.element-title {
font-size: 1.3em;
font-weight: 600;
margin-bottom: 8px;
}
.element-meta {
display: flex;
gap: 15px;
align-items: center;
}
.element-id {
font-size: 0.9em;
opacity: 0.8;
font-family: 'Monaco', 'Consolas', monospace;
}
.element-content {
padding: 25px;
background: white;
}
.element-content h1,
.element-content h2,
.element-content h3,
.element-content h4,
.element-content h5,
.element-content h6 {
color: #262c33;
margin-top: 20px;
margin-bottom: 12px;
font-weight: 600;
}
.element-content p {
margin-bottom: 15px;
line-height: 1.7;
}
.element-content ul,
.element-content ol {
margin-bottom: 15px;
padding-left: 25px;
}
.element-content li {
margin-bottom: 8px;
line-height: 1.6;
}
.element-content code {
background: #fafafa;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.9em;
color: #262c33;
}
.element-content pre {
background: #fafafa;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
margin-bottom: 15px;
border-left: 3px solid #bfcbd9;
}
.element-content blockquote {
border-left: 3px solid #bfcbd9;
padding-left: 15px;
margin: 15px 0;
color: #666;
font-style: italic;
background: #fafafa;
padding: 12px 15px;
border-radius: 0 4px 4px 0;
}
.element-content table {
width: 100%;
border-collapse: collapse;
margin-bottom: 15px;
border: 1px solid #dae2ec;
border-radius: 4px;
overflow: hidden;
}
.element-content th,
.element-content td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid #dae2ec;
}
.element-content th {
background: #fafafa;
color: #262c33;
font-weight: 600;
}
.element-content tr:hover {
background: #fafafa;
}
.element-content a {
color: #262c33;
text-decoration: none;
font-weight: 500;
}
.element-content a:hover {
color: #666;
text-decoration: underline;
}
.no-documentation {
color: #666;
font-style: italic;
text-align: center;
padding: 15px;
background: #fafafa;
border-radius: 4px;
}
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
display: none;
z-index: 1000;
}
.back-to-top-btn {
background: white;
color: #262c33;
border: 1px solid #dae2ec;
padding: 10px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.3s ease;
}
.back-to-top-btn:hover {
background: #fafafa;
}
@media (max-width: 768px) {
.container {
margin: 0;
box-shadow: none;
}
.header {
padding: 40px 20px;
}
.main-title {
font-size: 2.2em;
}
.stats-section,
.diagram-section,
.toc-section,
.documentation-section {
padding: 20px;
}
.stats-grid {
grid-template-columns: 1fr;
}
.toc-list {
columns: 1;
}
.element-header {
padding: 20px;
flex-direction: column;
text-align: center;
gap: 10px;
}
.element-meta {
justify-content: center;
}
.element-content {
padding: 20px;
}
}
@media print {
body {
background: white;
}
.container {
box-shadow: none;
}
.back-to-top {
display: none;
}
.element-section {
page-break-inside: avoid;
break-inside: avoid;
}
}
`;
}
/**
* Escape HTML special characters
*/
_escapeHtml(unsafe) {
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
/**
* Get process information from BPMN diagram
*/
_getProcessInfo() {
try {
const rootElement = this._canvas.getRootElement();
if (rootElement?.businessObject) {
const bo = rootElement.businessObject;
return {
name: bo.name || null,
id: bo.id || null,
filename: "process.bpmn",
element: rootElement
};
}
return {
name: null,
id: null,
filename: "process.bpmn",
element: null
};
} catch (error) {
console.error("Error getting process info:", error);
return {
name: null,
id: null,
filename: "process.bpmn",
element: null
};
}
}
/**
* Download file to user's system
*/
_downloadFile(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* Show notification to user
*/
_showNotification(message, type) {
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
notification.textContent = message;
Object.assign(notification.style, {
position: "fixed",
top: "20px",
right: "20px",
padding: "12px 16px",
borderRadius: "4px",
color: "white",
fontSize: "14px",
fontWeight: "500",
zIndex: "10000",
maxWidth: "300px",
wordWrap: "break-word",
backgroundColor: type === "success" ? "#28a745" : type === "warning" ? "#ffc107" : "#dc3545",
boxShadow: "0 2px 8px rgba(0,0,0,0.2)"
});
this._canvas.getContainer().appendChild(notification);
setTimeout(() => {
if (notification.parentNode) notification.parentNode.removeChild(notification);
}, 3e3);
}
destroy() {}
};
//#endregion
//#region src/extension/managers/OverviewManager.ts
var OverviewManager = class {
_callbacks;
_currentFilter = "all";
_currentSearchTerm = "";
constructor(options) {
this._callbacks = options.callbacks;
}
setupOverviewEventListeners() {
setTimeout(() => {
document.getElementById("overview-search")?.addEventListener("input", (event) => {
this.filterOverviewList(event.target.value);
});
document.getElementById("show-all")?.addEventListener("click", () => {
this.setOverviewFilter("all");
});
document.getElementById("show-documented")?.addEventListener("click", () => {
this.setOverviewFilter("documented");
});
document.getElementById("show-undocumented")?.addEventListener("click", () => {
this.setOverviewFilter("undocumented");
});
}, 100);
}
refreshOverview() {
this._updateFilterButtonStates();
this._updateCoverageStats();
this._updateOverviewList();
}
filterOverviewList(searchTerm) {
this._currentSearchTerm = searchTerm;
this._updateOverviewList();
}
setOverviewFilter(filter) {
this._currentFilter = filter;
const sidebar = this._callbacks.getSidebar();
if (!sidebar) return;
sidebar.querySelectorAll(".btn-small").forEach((btn) => {
btn.classList.remove("active");
});
const activeButton = sidebar.querySelector(`#show-${filter}`);
if (activeButton) activeButton.classList.add("active");
this._updateOverviewList();
}
destroy() {}
_updateFilterButtonStates() {
const sidebar = this._callbacks.getSidebar();
if (!sidebar) return;
sidebar.querySelectorAll(".btn-small").forEach((btn) => {
btn.classList.remove("active");
});
const activeButton = sidebar.querySelector(`#show-${this._currentFilter}`);
if (activeButton) activeButton.classList.add("active");
}
_updateCoverageStats() {
const sidebar = this._callbacks.getSidebar();
if (!sidebar) return;
const elements = this._getAllElementsWithDocumentation();
const documentedCount = elements.filter((el) => el.hasDocumentation).length;
const totalCount = elements.length;
const percentage = totalCount > 0 ? Math.round(documentedCount / totalCount * 100) : 0;
const documentedCountEl = sidebar.querySelector("#documented-count");
if (documentedCountEl) documentedCountEl.textContent = documentedCount.toString();
const totalCountEl = sidebar.querySelector("#total-count");
if (totalCountEl) totalCountEl.textContent = totalCount.toString();
const coveragePercentageEl = sidebar.querySelector("#coverage-percentage");
if (coveragePercentageEl) coveragePercentageEl.textContent = `${percentage}%`;
const progressBar = sidebar.querySelector("#coverage-progress");
if (progressBar) progressBar.style.width = `${percentage}%`;
}
_getAllElementsWithDocumentation() {
const elements = [];
const seenIds = /* @__PURE__ */ new Set();
const allElements = this._callbacks.getAllElements();
allElements.forEach((element) => {
if (element.businessObject?.id) {
const bo = element.businessObject;
const elementId = bo.id;
if (seenIds.has(elementId)) return;
seenIds.add(elementId);
const documentation = this._callbacks.getElementDocumentation(element);
elements.push({
id: elementId,
name: bo.name || "Unnamed",
type: this._callbacks.getElementTypeName(element),
hasDocumentation: !!documentation?.trim(),
documentation: documentation || "",
element
});
}
});
return elements.sort((a, b) => a.id.localeCompare(b.id));
}
_updateOverviewList() {
const sidebar = this._callbacks.getSidebar();
if (!sidebar) return;
const overviewList = sidebar.querySelector("#overview-list");
if (!overviewList) return;
const elements = this._getAllElementsWithDocumentation();
let filteredElements = elements;
if (this._currentFilter === "documented") filteredElements = elements.filter((el) => el.hasDocumentation);
else if (this._currentFilter === "undocumented") filteredElements = elements.filter((el) => !el.hasDocumentation);
if (this._currentSearchTerm) {
const searchTerm = this._currentSearchTerm.toLowerCase();
filteredElements = filteredElements.filter((el) => el.id.toLowerCase().includes(searchTerm) || el.name.toLowerCase().includes(searchTerm) || el.documentation.toLowerCase().includes(searchTerm));
}
if (filteredElements.length === 0) {
overviewList.innerHTML = "<div class=\"overview-loading\">No elements found</div>";
return;
}
overviewList.innerHTML = filteredElements.map((element) => {
const statusClass = element.hasDocumentation ? "documented" : "undocumented";
const statusText = element.hasDocumentation ? "documented" : "undocumented";
return `
<div class="element-item ${statusClass}" data-element-id="${element.id}">
<div class="element-header">
<span class="element-id">${element.id}</span>
<span class="element-status ${statusClass}">${statusText}</span>
</div>
<div class="element-info">
<span>${element.name}</span>
<span>•</span>
<span>${element.type}</span>
</div>
</div>
`;
}).join("");
overviewList.querySelectorAll(".element-item").forEach((card) => {
card.addEventListener("click", () => {
const elementId = card.getAttribute("data-element-id");
if (elementId) {
this._callbacks.selectElementById(elementId);
this._callbacks.switchToElementTab();
}
});
});
}
};
//#endregion
//#region src/extension/managers/SidebarManager.ts
var SidebarManager = class {
_canvas;
_htmlGenerator;
_onSidebarReady;
_sidebar = null;
_resizeObserver = null;
_cleanupRaf = null;
_isResizing = false;
_resizeStartX = 0;
_resizeStartWidth = 0;
_isVerticalResizing = false;
_resizeStartY = 0;
_resizeStartHeight = 0;
_customWidth = null;
_wasVisible = false;
constructor(options) {
this._canvas = options.canvas;
this._htmlGenerator = options.htmlGenerator;
this._onSidebarReady = options.onSidebarReady;
}
initializeSidebar() {
const existingSidebar = document.getElementById("documentation-sidebar");
if (existingSidebar) existingSidebar.remove();
const existingHandle = document.getElementById("horizontal-resize-handle");
if (existingHandle) existingHandle.remove();
const existingHelpPopover = document.getElementById("help-popover");
if (existingHelpPopover) existingHelpPopover.remove();
const sidebar = document.createElement("div");
sidebar.id = "documentation-sidebar";
sidebar.className = "documentation-sidebar";
sidebar.style.display = "none";
sidebar.innerHTML = this._htmlGenerator.generateSidebarHTML();
const canvasContainer = this._getCanvasContainer();
const currentPosition = window.getComputedStyle(canvasContainer).position;
if (currentPosition === "static") canvasContainer.style.position = "relative";
canvasContainer.appendChild(sidebar);
this._sidebar = sidebar;
const helpPopoverDiv = document.createElement("div");
helpPopoverDiv.innerHTML = this._htmlGenerator.generateHelpPopoverHTML();
const helpPopover = helpPopoverDiv.firstElementChild;
canvasContainer.appendChild(helpPopover);
const horizontalResizeHandle = document.createElement("div");
horizontalResizeHandle.id = "horizontal-resize-handle";
horizontalResizeHandle.className = "horizontal-resize-handle";
canvasContainer.appendChild(horizontalResizeHandle);
if (this._onSidebarReady) setTimeout(() => {
this._onSidebarReady?.(sidebar);
}, 10);
setTimeout(() => {
this.updateSidebarPosition();
this.setupResizeObserver();
this.setupResizeHandles();
}, 100);
}
showSidebar() {
if (this._sidebar && !this._sidebar.parentElement) {
this.initializeSidebar();
setTimeout(() => {
this.showSidebar();
}, 10);
return;
}
this.updateSidebarPosition();
if (this._sidebar) {
this._sidebar.style.display = "flex";
this._sidebar.classList.add("visible");
}
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (horizontalHandle) horizontalHandle.style.display = "block";
this._wasVisible = true;
}
hideSidebar() {
if (this._sidebar) {
this._wasVisible = this._sidebar.classList.contains("visible");
this._sidebar.classList.remove("visible");
this._sidebar.style.display = "none";
}
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (horizontalHandle) horizontalHandle.style.display = "none";
}
updateSidebarPosition() {
const canvasContainer = this._getCanvasContainer();
const containerRect = canvasContainer.getBoundingClientRect();
const propertiesPanel = document.querySelector(".bio-properties-panel-container") || document.querySelector(".djs-properties-panel") || document.querySelector("[data-tab=\"properties\"]") || document.querySelector(".properties-panel");
if (propertiesPanel) {
const panelRect = propertiesPanel.getBoundingClientRect();
const topOffset = Math.max(panelRect.top - containerRect.top, 0);
const bottomOffset = Math.max(containerRect.bottom - panelRect.bottom, 0);
const availableHeight = containerRect.height - topOffset - bottomOffset;
if (this._sidebar) {
this._sidebar.style.top = `${topOffset}px`;
this._sidebar.style.height = `${Math.max(availableHeight, 300)}px`;
if (!this._isResizing) {
const width = this._customWidth ? `${this._customWidth}px` : "350px";
this._sidebar.style.setProperty("width", width, "important");
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (horizontalHandle) {
const sidebarWidth = this._customWidth || 350;
horizontalHandle.style.right = `${sidebarWidth}px`;
horizontalHandle.style.top = `${topOffset}px`;
horizontalHandle.style.height = `${Math.max(availableHeight, 900)}px`;
}
}
}
} else if (this._sidebar) {
this._sidebar.style.right = "0px";
this._sidebar.style.top = "0px";
this._sidebar.style.height = "100%";
if (!this._isResizing) {
const width = this._customWidth ? `${this._customWidth}px` : "350px";
this._sidebar.style.setProperty("width", width, "important");
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (horizontalHandle) {
const sidebarWidth = this._customWidth || 350;
horizontalHandle.style.right = `${sidebarWidth}px`;
horizontalHandle.style.top = "0px";
horizontalHandle.style.height = "100%";
}
}
}
}
setupResizeObserver() {
if (this._resizeObserver) this._resizeObserver.disconnect();
const propertiesPanel = document.querySelector(".bio-properties-panel-container") || document.querySelector(".djs-properties-panel") || document.querySelector("[data-tab=\"properties\"]") || document.querySelector(".properties-panel");
if (propertiesPanel && window.ResizeObserver) {
this._resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => {
this.updateSidebarPosition();
});
});
this._resizeObserver.observe(propertiesPanel);
const parentContainer = propertiesPanel.parentElement;
if (parentContainer) this._resizeObserver.observe(parentContainer);
const rightPanel = document.querySelector(".djs-properties-panel-parent") || document.querySelector(".properties-panel-parent") || propertiesPanel.closest(".panel");
if (rightPanel && rightPanel !== propertiesPanel) this._resizeObserver.observe(rightPanel);
}
let rafId;
const updatePosition = () => {
if (this._sidebar?.classList.contains("visible") && this._sidebar.style.display !== "none" && !this._isResizing && !this._isVerticalResizing) this.updateSidebarPosition();
rafId = requestAnimationFrame(updatePosition);
};
updatePosition();
this._cleanupRaf = () => {
if (rafId) cancelAnimationFrame(rafId);
};
window.addEventListener("resize", () => {
this.updateSidebarPosition();
});
}
setupResizeHandles() {
this._setupHorizontalResize();
this._setupVerticalResize();
}
getSidebar() {
return this._sidebar;
}
isSidebarVisible() {
return this._sidebar?.classList.contains("visible") ?? false;
}
destroy() {
if (this._resizeObserver) this._resizeObserver.disconnect();
if (this._cleanupRaf) this._cleanupRaf();
if (this._sidebar) this._sidebar.remove();
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (horizontalHandle) horizontalHandle.remove();
const helpPopover = document.getElementById("help-popover");
if (helpPopover) helpPopover.remove();
}
_getCanvasContainer() {
return this._canvas?.getContainer() ?? document.body;
}
_setupHorizontalResize() {
const horizontalHandle = document.getElementById("horizontal-resize-handle");
if (!horizontalHandle) return;
let rafId = null;
let pendingWidth = null;
const handleMouseDown = (e) => {
e.preventDefault();
this._isResizing = true;
this._resizeStartX = e.clientX;
this._resizeStartWidth = this._sidebar?.offsetWidth || 350;
document.body.style.cursor = "ew-resize";
document.body.style.userSelect = "none";
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
const updateResize = () => {
if (pendingWidth !== null && this._sidebar) {
this._sidebar.style.setProperty("width", `${pendingWidth}px`, "important");
const horizontalHandle$1 = document.getElementById("horizontal-resize-handle");
if (horizontalHandle$1) horizontalHandle$1.style.right = `${pendingWidth}px`;
pendingWidth = null;
}
rafId = null;
};
const handleMouseMove = (e) => {
if (!this._isResizing || !this._sidebar) return;
e.preventDefault();
const deltaX = this._resizeStartX - e.clientX;
const newWidth = this._resizeStartWidth + deltaX;
const minWidth = 250;
const maxWidth = window.innerWidth * .6;
const constrainedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
this._customWidth = constrainedWidth;
pendingWidth = constrainedWidth;
if (rafId === null) rafId = requestAnimationFrame(updateResize);
};
const handleMouseUp = () => {
this._isResizing = false;
document.body.style.cursor = "";
document.body.style.userSelect = "";
if (rafId !== null) {
cancelAnimationFrame(rafId);
updateResize();
}
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
horizontalHandle.addEventListener("mousedown", handleMouseDown);
}
_setupVerticalResize() {
const verticalHandle = document.getElementById("resize-handle");
if (!verticalHandle) return;
let rafId = null;
let pendingHeight = null;
const handleMouseDown = (e) => {
e.preventDefault();
this._isVerticalResizing = true;
this._resizeStartY = e.clientY;
const previewElement = document.getElementById("doc-preview");
this._resizeStartHeight = previewElement?.offsetHeight || 200;
document.body.style.cursor = "ns-resize";
document.body.style.userSelect = "none";
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
const updateResize = () => {
if (pendingHeight !== null) {
const previewElement = document.getElementById("doc-preview");
if (previewElement) previewElement.style.height = `${pendingHeight}px`;
pendingHeight = null;
}
rafId = null;
};
const handleMouseMove = (e) => {
if (!this._isVerticalResizing) return;
e.preventDefault();
const deltaY = e.clientY - this._resizeStartY;
const newHeight = this._resizeStartHeight + deltaY;
const minHeight = 100;
const maxHeight = window.innerHeight * .6;
const constrainedHeight = Math.max(minHeight, Math.min(maxHeight, newHeight));
pendingHeight = constrainedHeight;
if (rafId === null) rafId = requestAnimationFrame(updateResize);
};
const handleMouseUp = () => {
this._isVerticalResizing = false;
document.body.style.cursor = "";
document.body.style.userSelect = "";
if (rafId !== null) {
cancelAnimationFrame(rafId);
updateResize();
}
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
verticalHandle.addEventListener("mousedown", handleMouseDown);
}
};
//#endregion
//#region src/extension/managers/TabManager.ts
var TabManager = class {
_callbacks;
constructor(options) {
this._callbacks = options.callbacks;
}
setupTabEventListeners() {
setTimeout(() => {
document.getElementById("element-tab")?.addEventListener("click", () => {
this.switchTab("element");
});
document.getElementById("overview-tab")?.addEventListener("click", () => {
this.switchTab("overview");
this._callbacks.onOverviewTabActivated();
});
}, 100);
}
switchTab(tabName) {
if (!this._callbacks.isSidebarVisible()) return;
const sidebar = this._callbacks.getSidebar();
if (!sidebar) return;
Array.from(sidebar.querySelectorAll(".tab-btn")).forEach((btn) => {
const btnEl = btn;
if (btnEl.dataset.tab === tabName) btnEl.classList.add("active");
else btnEl.classList.remove("active");
});
Array.from(sidebar.querySelectorAll(".tab-panel")).forEach((panel) => {
const panelEl = panel;
if (panelEl.id === `${tabName}-panel`) panelEl.classList.add("active");
else panelEl.classList.remove("active");
});
const elementMetadata = sidebar.querySelector("#element-metadata");
if (elementMetadata) elementMetadata.style.display = "block";
if (tabName === "element") this._callbacks.onElementTabActivated();
}
destroy() {}
};
//#endregion
//#region src/extension/managers/ViewManager.ts
var ViewManager = class {
_currentView = "diagram";
_viewCheckInterval = null;
_callbacks;
constructor(callbacks) {
this._callbacks = callbacks;
}
getCurrentView() {
return this._currentView;
}
setupViewDetection() {
this._viewCheckInterval = setInterval(() => {
const newView = this._detectCurrentView();
if (newView !== this._currentView) {
this._currentView = newView;
this._callbacks.onViewChanged(newView);
this.updateSidebarVisibility();
}
}, 500);
}
updateSidebarVisibility() {
if (this._currentView === "xml") this._callbacks.hideSidebar();
else if (this._currentView === "diagram") {
const currentElement = this._callbacks.getCurrentElement();
if (currentElement) {
const documentation = this._callbacks.getElementDocumentation(currentElement);
this._callbacks.showSidebar(documentation || "");
}
}
}
destroy() {
if (this._viewCheckInterval) {
clearInterval(this._viewCheckInterval);
this._viewCheckInterval = null;
}
}
_detectCurrentView() {
const xmlEditor = document.querySelector(".cm-editor");
const codeEditor = document.querySelector(".CodeMirror");
if (xmlEditor && xmlEditor.offsetParent !== null || codeEditor && codeEditor.offsetParent !== null) return "xml";
return "diagram";
}
};
//#endregion
//#region src/extension/templates/HtmlTemplateGenerator.ts
var HtmlTemplateGenerator = class {
_options;
constructor(options) {
this._options = options;
}
generateSidebarHTML() {
const editorSection = this._generateEditorSection();
return `
<div class="documentation-header">
<div class="header-content">
<div class="title-row">
<h3>Documentation${this._options.isModeler ? "" : " (Read-only)"}</h3>
<button class="help-btn" id="help-btn">?</button>
</div>
<div class="element-metadata" id="element-metadata">
<span class="element-name" id="element-name"></span>
</div>
</div>
<button class="close-btn" id="close-sidebar">×</button>
</div>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-btn active" id="element-tab" data-tab="element">Element</button>
<button class="tab-btn" id="overview-tab" data-tab="overview">Overview</button>
</div>
<button class="btn-export" id="export-btn">
<span class="export-btn-icon">📤</span>
<span>Export</span>
</button>
</div>
<div class="tab-content">
<div class="tab-panel active" id="element-panel">
<div class="documentation-content">
<div class="documentation-preview" id="doc-preview"></div>
${editorSection}
</div>
</div>
<div class="tab-panel" id="overview-panel">
<div class="overview-content">
<div class="overview-header">
<div class="coverage-summary">
<div class="coverage-stats">
<span class="stat-item">
<strong id="documented-count">0</strong> documented
</span>
<span class="stat-item">
<strong id="total-count">0</strong> total elements
</span>
<span class="stat-item">
<strong id="coverage-percentage">0%</strong> coverage
</span>
</div>
<div class="coverage-bar">
<div class="coverage-progress" id="coverage-progress"></div>
</div>
</div>
<div class="overview-search">
<input type="text" id="overview-search" placeholder="Search documentation..." />
<div class="search-actions">
<button class="btn-small" id="show-documented">Documented</button>
<button class="btn-small" id="show-undocumented">Undocumented</button>
<button class="btn-small active" id="show-all">All</button>
</div>
</div>
</div>
<div class="overview-list" id="overview-list">
<div class="overview-loading">Loading elements...</div>
</div>
</div>
</div>
</div>
`;
}
generateHelpPopoverHTML() {
const helpContent = this._generateHelpContent();
return `
<div class="help-popover" id="help-popover">
<div class="help-content">
<h4>Documentation Panel Guide</h4>
${helpContent}
</div>
</div>
`;
}
_generateHelpContent() {
return this._options.isModeler ? `
<div class="help-section">
<strong>What is this panel?</strong>
<p>This panel allows you to add and view documentation for BPMN elements in your diagram.</p>
</div>
<div class="help-section">
<strong>How to use:</strong>
<ul>
<li><strong>Select an element</strong> - Click on any BPMN element (task, gateway, event, etc.) to view or edit its documentation</li>
<li><strong>Edit documentation</strong> - Use the editor below to write documentation in Markdown format</li>
<li><strong>Preview</strong> - The top section shows a live preview of your formatted documentation</li>
</ul>
</div>
<div class="help-section">
<strong>Markdown support:</strong>
<p>Use standard Markdown syntax for formatting: **bold**, *italic*, lists, links, and more.</p>
</div>
<div class="help-section">
<strong>Creating links:</strong>
<p>Link to other BPMN elements using their ID:</p>
<code>[Element Name](#elementId)</code>
<p>Example: <code>[Check Inventory](#Task_CheckInventory)</code></p>
<p>Link to external resources:</p>
<code>[External Link](https://example.com)</code>
<p><em>Tip: Element IDs can be found in the properties panel or by selecting the element. Type # inside () for autocomplete suggestions.</em></p>
</div>
` : `
<div class="help-section">
<strong>What is this panel?</strong>
<p>This panel displays documentation for BPMN elements in this diagram.</p>
</div>
<div class="help-section">
<strong>How to use:</strong>
<ul>
<li><strong>Select an element</strong> - Click on any BPMN element (task, gateway, event, etc.) to view its documentation</li>
<li><strong>Navigate</strong> - Click on element links to jump to related elements in the diagram</li>
<li><strong>Overview</strong> - Use the Overview tab to see all documented elements</li>
</ul>
</div>
<div class="help-section">
<strong>Reading documentation:</strong>
<p>Documentation is displayed in formatted view. Click on element links (shown in blue) to navigate to related elements in the diagram.</p>
</div>
`;
}
_generateEditorSection() {
return this._options.isModeler ? `
<div class="documentation-bottom">
<div class="resize-handle" id="resize-handle"></div>
<div class="documentation-editor">
<div class="editor-container">
<textarea id="doc-textarea" placeholder="Write documentation in Markdown..."></textarea>
<div class="autocomplete-dropdown" id="autocomplete-dropdown">
<div class="autocomplete-list" id="autocomplete-list"></div>
</div>
</div>
</div>
</div>
` : "";
}
};
//#endregion
//#region src/extension/index.ts
var DocumentationExtension = class {
_eventBus;
_elementRegistry;
_modeling;
_moddle;
_selection;
_canvas;
_currentElement;
_isModeler;
_currentView;
_viewManager;
_htmlGenerator;
_sidebarManager;
_tabManager;
_overviewManager;
_autocompleteManager;
_exportManager;
constructor(eventBus, elementRegistry, injector, moddle, selection, canvas) {
this._modeling = injector.get("modeling", false);
this._isModeler = !!this._modeling;
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
this._moddle = moddle;
this._selection = selection;
this._canvas = canvas;
this._currentElement = null;
this._currentView = "diagram";
this._htmlGenerator = new HtmlTemplateGenerator({ isModeler: this._isModeler });
this._sidebarManager = new SidebarManager({
canvas: this._canvas,
htmlGenerator: this._htmlGenerator,
onSidebarReady: () => this._onSidebarReady()
});
const tabCallbacks = {
onOverviewTabActivated: () => this._overviewManager.refreshOverview(),
onElementTabActivated: () => {},
getSidebar: () => this._sidebarManager.getSidebar(),
isSidebarVisible: () => this._sidebarManager.isSidebarVisible()
};
this._tabManager = new TabManager({ callbacks: tabCallbacks });
const viewCallbacks = {
onViewChanged: (newView) => {
this._currentView = newView;
},
hideSidebar: () => this._sidebarManager.hideSidebar(),
showSidebar: (documentation) => this._showSidebar(documentation),
getElementDocumentation: (element) => this._getElementDocumentation(element),
getCurrentElement: () => this._currentElement
};
this._viewManager = new ViewManager(viewCallbacks);
const overviewCallbacks = {
getAllElements: () => this._elementRegistry.getAll(),
getElementDocumentation: (element) => this._getElementDocumentation(element),
getElementTypeName: (element) => this._getElementTypeName(element),
getSidebar: () => this._sidebarManager.getSidebar(),
selectElementById: (elementId) => this._selectElementById(elementId),
switchToElementTab: () => this._tabManager.switchTab("element")
};
this._overviewManager = new OverviewManager({ callbacks: overviewCallbacks });
const autocompleteCallbacks = {
getAllElements: () => this._elementRegistry.getAll(),
getElementTypeName: (element) => this._getElementTypeName(element),
getCanvasContainer: () => this._getCanvasContainer(),
updatePreview: () => this._updatePreview(),
saveDocumentationLive: () => this._saveDocumentationLive(),
selectElementById: (elementId) => this._selectElementById(elementId),
getCurrentElement: () => this._currentElement
};
this._autocompleteManager = new AutocompleteManager({ callbacks: autocompleteCallbacks });
this._exportManager = new ExportManager(this._elementRegistry, this._moddle, this._canvas);
this._sidebarManager.initializeSidebar();
this._viewManager.setupViewDetection();
this._exportManager.setupExportEventListeners();
eventBus.on("element.click", (event) => {
const { element } = event;
this._handleElementClick(element);
});
eventBus.on("selection.changed", (event) => {
const { newSelection } = event;
if (newSelection && newSelection.length > 0) this._handleElementClick(newSelection[0]);
else this._sidebarManager.hideSidebar();
});
eventBus.on("connection.click", (event) => {
const { element } = event;
this._handleElementClick(element);
});
eventBus.on("canvas.click", () => {
setTimeout(() => {
if (!this._currentElement) this._sidebarManager.hideSidebar();
}, 10);
});
eventBus.on("import.done", () => {
this._handleDiagramImport();
});
eventBus.on("import.parse.complete", () => {
this._handleDiagramImport();
});
eventBus.on("diagram.destroy", () => {
this._handleDiagramDestroy();
});
eventBus.on("diagram.clear", () => {
this._handleDiagramClear();
});
}
_onSidebarReady() {
const helpBtn = document.getElementById("help-btn");
if (helpBtn) {
helpBtn.replaceWith(helpBtn.cloneNode(true));
const newHelpBtn = document.getElementById("help-btn");
newHelpBtn?.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
this._toggleHelpPopover();
});
}
document.addEventListener("click", (event) => {
const helpPopover = document.getElementById("help-popover");
const helpBtn$1 = document.getElementById("help-btn");
if (helpPopover?.classList.contains("visible") && !(event.target instanceof Node && helpPopover.contains(event.target)) && !(event.target instanceof Node && helpBtn$1 && helpBtn$1.contains(event.target))) this._hideHelpPopover();
});
if (this._isModeler) {
const textarea = document.getElementById("doc-textarea");
if (textarea) textarea.addEventListener("input", () => {
this._updatePreview();
this._saveDocumentationLive();
});
this._autocompleteManager.setupAutocompleteEventListeners();
}
setTimeout(() => {
document.getElementById("close-sidebar")?.addEventListener("click", () => {
this._currentElement = null;
this._sidebarManager.hideSidebar();
});
}, 100);
this._tabManager.setupTabEventListeners();
this._overviewManager.setupOverviewEventListeners();
this._setupScrollEventHandling();
}
_setupScrollEventHandling() {
setTimeout(() => {
const sidebar = document.getElementById("documentation-sidebar");
if (sidebar) {
sidebar.addEventListener("wheel", (event) => {
if (event.target instanceof Element && sidebar.contains(event.target)) event.stopPropagation();
}, { passive: false });
sidebar.addEventListener("scroll", (event) => {
if (event.target instanceof Element && sidebar.contains(event.target)) event.stopPropagation();
}, { passive: true });
const sidebarScrollableSelectors = [
"#doc-preview",
"#overview-list",
"#doc-textarea",
"#autocomplete-dropdown"
];
sidebarScrollableSelectors.forEach((selector) => {
const element = sidebar.querySelector(selector);
if (element) {
element.addEventListener("wheel", (event) => {
event.stopPropagation();
}, { passive: false });
element.addEventListener("scroll", (event) => {
event.stopPropagation();
}, { passive: true });
}
});
}
const helpPopover = document.getElementById("help-popover");
if (helpPopover) {
helpPopover.addEventListener("wheel", (event) => {
event.stopPropagation();
}, { passive: false });
helpPopover.addEventListener("scroll", (event) => {
event.stopPropagation();
}, { passive: true });
}
}, 150);
}
_handleElementClick(element) {
if (this._currentView === "xml") return;
if (!element || !element.businessObject) {
this._currentElement = null;
this._sidebarManager.hideSidebar();
return;
}
const documentation = this._getElementDocumentation(element);
const hasCapability = this._hasDocumentationCapability(element);
if (documentation || hasCapability) {
this._currentElement = element;
this._showSidebar(documentation || "");
} else {
this._currentElement = null;
this._sidebarManager.hideSidebar();
}
}
_getElementDocumentation(element) {
const businessObject = element.businessObject;
if (businessObject.documentation && businessObject.documentation.length > 0) return businessObject.documentation[0].text;
return null;
}
_hasDocumentationCapability(element) {
return element.businessObject;
}
_showSidebar(documentation) {
if (this._isModeler) {
const textarea = document.getElementById("doc-textarea");
if (textarea) textarea.value = documentation || "";
}
this._updateElementMetadata();
this._updatePreview();
this._sidebarManager.showSidebar();
}
_hideSidebar() {
this._sidebarManager.hideSidebar();
}
_toggleHelpPopover() {
const helpPopover = document.getElementById("help-popover");
console.log("Toggle help popover:", helpPopover?.classList.contains("visible"));
if (helpPopover?.classList.contains("visible")) this._hideHelpPopover();
else if (helpPopover) this._showHelpPopover();
}
_showHelpPopover() {
const helpPopover = document.getElementById("help-popover");
if (helpPopover) helpPopover.classList.add("visible");
}
_hideHelpPopover() {
const helpPopover = document.getElementById("help-popover");
if (helpPopover) helpPopover.classList.remove("visible");
}
_getCanvasContainer() {
return this._canvas?.getContainer() ?? document.body;
}
async _updatePreview() {
const preview = document.getElementById("doc-preview");
if (!preview) return;
let value = "";
if (this._isModeler) {
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
value = textarea.value;
} else if (this._currentElement) value = this._getElementDocumentation(this._currentElement) || "";
if (value?.trim()) {
const rendered = await Promise.resolve(marked(value));
preview.innerHTML = typeof rendered === "string" ? rendered : "";
this._setupElementLinks(preview);
} else preview.innerHTML = "<em>No documentation.</em>";
}
_setupElementLinks(container) {
const links = container.querySelectorAll("a[href^=\"#\"]");
links.forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
const elementId = link.getAttribute("href")?.substring(1);
if (elementId) this._selectElementById(elementId);
});
link.addEventListener("mouseenter", () => {
link.classList.add("hovered");
});
link.addEventListener("mouseleave", () => {
link.classList.remove("hovered");
});
});
}
_selectElementById(elementId) {
try {
const element = this._elementRegistry.get(elementId);
if (element) {
this._selection.select(element);
this._canvas.scrollToElement(element);
} else {
console.warn(`Element with ID "${elementId}" not found in the diagram`);
this._showLinkNotification(`Element "${elementId}" not found`);
}
} catch (error) {
console.error("Error selecting element:", error);
this._showLinkNotification(`Error selecting element "${elementId}"`);
}
}
_showLinkNotification(message) {
const notification = document.createElement("div");
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #f44336;
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 12px;
z-index: 10000;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
`;
this._getCanvasContainer().appendChild(notification);
setTimeout(() => {
if (notification.parentNode) notification.parentNode.removeChild(notification);
}, 3e3);
}
_saveDocumentationLive() {
if (!this._currentElement || !this._modeling) return;
const textarea = document.getElementById("doc-textarea");
if (!textarea) return;
const documentation = textarea.value;
try {
const documentationArray = [];
if (documentation.trim()) {
const docElement = this._moddle.create("bpmn:Documentation", { text: documentation });
documentationArray.push(docElement);
}
this._modeling.updateProperties(this._currentElement, { documentation: documentationArray });
} catch (error) {
console.error("Error saving documentation:", error);
}
}
_updateElementMetadata() {
if (!this._currentElement) return;
const businessObject = this._currentElement.businessObject;
const elementId = businessObject.id || "Unknown ID";
const elementNameElement = document.getElementById("element-name");
if (elementNameElement) elementNameElement.textContent = elementId;
}
_getElementTypeName(element) {
if (is(element, "bpmn:Task")) return "Task";
if (is(element, "bpmn:UserTask")) return "User Task";
if (is(element, "bpmn:ServiceTask")) return "Service Task";
if (is(element, "bpmn:ScriptTask")) return "Script Task";
if (is(element, "bpmn:CallActivity")) return "Call Activity";
if (is(element, "bpmn:SubProcess")) return "Sub Process";
if (is(element, "bpmn:StartEvent")) return "Start Event";
if (is(element, "bpmn:EndEvent")) return "End Event";
if (is(element, "bpmn:IntermediateThrowEvent")) return "Intermediate Event";
if (is(element, "bpmn:IntermediateCatchEvent")) return "Intermediate Event";
if (is(element, "bpmn:Gateway")) return "Gateway";
if (is(element, "bpmn:ExclusiveGateway")) return "Exclusive Gateway";
if (is(element, "bpmn:ParallelGateway")) return "Parallel Gateway";
if (is(element, "bpmn:InclusiveGateway")) return "Inclusive Gateway";
if (is(element, "bpmn:SequenceFlow")) return "Sequence Flow";
if (is(element, "bpmn:MessageFlow")) return "Message Flow";
if (is(element, "bpmn:DataObject")) return "Data Object";
if (is(element, "bpmn:DataStore")) return "Data Store";
if (is(element, "bpmn:Lane")) return "Lane";
if (is(element, "bpmn:Participant")) return "Pool";
if (is(element, "bpmn:Process")) return "Process";
return "Element";
}
_handleDiagramImport() {
this._currentElement = null;
this._sidebarManager.hideSidebar();
this._sidebarManager.initializeSidebar();
}
_handleDiagramDestroy() {
this._cleanup();
}
_handleDiagramClear() {
this._currentElement = null;
this._sidebarManager.hideSidebar();
}
_cleanup() {
this._currentElement = null;
this._sidebarManager.hideSidebar();
}
destroy() {
this._cleanup();
this._sidebarManager.destroy();
this._viewManager.destroy();
this._autocompleteManager.destroy();
this._exportManager.destroy();
}
};
var extension_default = {
__init__: ["documentationExtension"],
documentationExtension: [
"type",
DocumentationExtension,
"eventBus",
"elementRegistry",
"injector",
"moddle",
"selection",
"canvas"
]
};
//#endregion
export { extension_default as DocumentationExtension };
//# sourceMappingURL=bpmn-js-entry.js.map