(() => {
var embed_default = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Skill Tree Builder</title>
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
:root {
--color-canvas-default: #0d1117;
--color-fg-default: #c9d1d9;
--color-border-default: #30363d;
--color-accent-emphasis: #1f6feb;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
background-color: var(--color-canvas-default);
color: var(--color-fg-default);
display: flex;
height: 100vh;
}
#cy {
flex: 1;
z-index: 1;
}
/* Instructions panel styles */
#instructions-container {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
}
#instructions-toggle {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
border: none;
padding: 8px;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin-bottom: 8px;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
#instructions-toggle:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
/* View toggle styles */
#view-toggle-container {
position: fixed;
top: 20px;
right: 320px;
z-index: 1000;
transition: right 0.3s ease;
}
.sidebar-collapsed #view-toggle-container {
right: 20px;
}
#view-toggle {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
border: none;
padding: 12px;
border-radius: 50%;
cursor: pointer;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
#view-toggle:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
#view-toggle:active {
transform: scale(0.95);
}
.view-toggle-icon {
transition: all 0.3s ease;
}
.view-toggle-icon.rotate-out {
transform: rotateY(90deg);
opacity: 0;
}
.view-toggle-icon.rotate-in {
transform: rotateY(0deg);
opacity: 1;
}
.info {
background: rgba(13, 17, 23, 0.9);
padding: 15px;
border-radius: 6px;
border: 1px solid var(--color-border-default);
font-size: 14px;
line-height: 1.5;
max-width: 350px;
transition: all 0.3s ease;
}
.info.hidden {
display: none;
}
/* Node tooltip styles */
#node-tooltip {
position: absolute;
background: rgba(13, 17, 23, 0.95);
color: var(--color-fg-default);
border: 1px solid var(--color-border-default);
border-radius: 6px;
padding: 8px 12px;
font-size: 14px;
line-height: 1.4;
max-width: 400px;
word-wrap: break-word;
white-space: pre-wrap;
z-index: 2000;
pointer-events: none;
display: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.modal {
display: none;
position: fixed;
z-index: 1001;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: var(--color-canvas-default);
margin: 15% auto;
padding: 20px;
border: 1px solid var(--color-border-default);
border-radius: 6px;
width: 50%;
max-width: 500px;
}
#modalInput {
width: 100%;
margin: 10px 0;
background-color: var(--color-canvas-default);
color: var(--color-fg-default);
border: 1px solid var(--color-border-default);
border-radius: 6px;
padding: 8px;
font-family: inherit;
}
button {
background-color: var(--color-accent-emphasis);
color: #ffffff;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
margin-right: 8px;
}
button.cancel {
background-color: #21262d;
}
.context-menu {
display: none;
position: absolute;
z-index: 1000;
background-color: var(--color-canvas-default);
border: 1px solid var(--color-border-default);
border-radius: 6px;
padding: 4px 0;
min-width: 160px;
}
.context-menu-item {
padding: 6px 12px;
cursor: pointer;
color: var(--color-fg-default);
font-size: 14px;
display: flex;
align-items: center;
}
.context-menu-item:hover {
background-color: var(--color-accent-emphasis);
color: #ffffff;
}
/* Restore original sidebar styles */
#editor-container {
position: fixed;
top: 0;
right: 0;
width: 300px;
height: 100vh;
background: var(--color-canvas-default);
border-left: 1px solid var(--color-border-default);
display: flex;
flex-direction: column;
z-index: 1000;
}
#json-viewer {
margin: 0;
padding: 16px;
width: calc(100% - 32px);
height: calc(100% - 32px);
background-color: var(--color-canvas-default);
color: var(--color-fg-default);
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
font-size: 14px;
line-height: 1.5;
border: none;
resize: none;
outline: none;
}
/* Adjust main container to account for sidebar */
#cy {
margin-right: 300px;
transition: margin-right 0.3s ease;
}
/* Collapsed state styles */
.sidebar-collapsed #editor-container {
transform: translateX(100%);
}
.sidebar-collapsed #cy {
margin-right: 0;
}
.sidebar-collapsed #eisenhower-matrix {
margin-right: 0;
}
/* Transition for smooth animation */
#editor-container {
transition: transform 0.3s ease;
}
/* Search Modal Styles */
.modal-search-results {
max-height: 300px;
overflow-y: auto;
margin: 10px 0;
border: 1px solid var(--color-border-default);
border-radius: 6px;
}
.search-result-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid var(--color-border-default);
font-size: 14px;
color: var(--color-fg-default);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover {
background-color: var(--color-accent-emphasis);
color: #ffffff;
}
.search-result-item.selected {
background-color: var(--color-accent-emphasis);
color: #ffffff;
}
.search-result-item.create-task-option {
background-color: var(--color-accent-subtle);
border-left: 3px solid var(--color-accent-emphasis);
font-style: italic;
}
.search-result-item.create-task-option:hover {
background-color: var(--color-accent-emphasis);
color: #ffffff;
}
#modal-search-input {
width: 100%;
margin: 10px 0;
background-color: var(--color-canvas-default);
color: var(--color-fg-default);
border: 1px solid var(--color-border-default);
border-radius: 6px;
padding: 8px;
font-family: inherit;
}
/* Node Selection Styles - More specific for Cytoscape */
.cy-node.selected-node {
border-color: #ff8c00 !important;
border-width: 3px !important;
border-style: solid !important;
}
.cy-node.ancestor-node {
border-width: 3px !important;
border-color: #4CAF50 !important;
border-style: solid !important;
}
.cy-node.dimmed-node {
opacity: 0.1 !important;
}
/* Fallback without cy-node prefix */
.selected-node {
border-color: #ff8c00 !important;
border-width: 3px !important;
border-style: solid !important;
}
.ancestor-node {
border-width: 3px !important;
border-color: #4CAF50 !important;
border-style: solid !important;
}
.dimmed-node {
opacity: 0.1 !important;
}
/* Eisenhower Matrix Styles */
#eisenhower-matrix {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: var(--color-canvas-default);
z-index: 10;
margin-right: 300px;
transition: margin-right 0.3s ease;
}
.eisenhower-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
width: 100%;
height: 100%;
gap: 2px;
}
.quadrant {
background-color: var(--color-canvas-subtle);
border: 2px solid var(--color-border-default);
padding: 20px;
display: flex;
flex-direction: column;
position: relative;
min-height: 0; /* Allow flexbox to shrink */
overflow: hidden; /* Prevent quadrant from expanding */
}
.quadrant h3 {
margin: 0 0 20px 0;
font-size: 36px;
font-weight: bold;
text-align: center;
color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
line-height: 1.2;
}
.quadrant p {
margin: 0 0 20px 0;
font-size: 14px;
color: var(--color-fg-muted);
}
.quadrant .task-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
min-height: 0; /* Allow scrolling when content overflows */
padding-right: 4px; /* Space for scrollbar */
}
/* Custom scrollbar for task lists */
.quadrant .task-list::-webkit-scrollbar {
width: 6px;
}
.quadrant .task-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.quadrant .task-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.quadrant .task-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* Task item styles */
.task-item {
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 12px;
cursor: grab;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0; /* Prevent tasks from shrinking when scrolling */
}
.task-item:hover {
background-color: rgba(255, 255, 255, 1);
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
border-color: rgba(255, 255, 255, 0.8);
}
.task-item:active {
cursor: grabbing;
}
/* Drag and drop styles */
.quadrant.drag-over {
background-color: rgba(99, 179, 237, 0.1);
border-color: #63b3ed;
border-style: dashed;
border-width: 3px;
}
.quadrant.drag-over .task-list {
background-color: rgba(99, 179, 237, 0.05);
border-radius: 4px;
}
.task-content {
font-size: 14px;
color: #2c3e50;
margin-bottom: 8px;
line-height: 1.4;
font-weight: 500;
}
.task-content.completed {
text-decoration: line-through;
opacity: 0.6;
color: var(--color-fg-muted);
}
.task-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.task-properties {
font-size: 12px;
color: #34495e;
opacity: 0.9;
}
/* Color coding for quadrants */
#quadrant-urgent-important {
background-color: #79b3c7;
border-color: #5a9fb5;
color: white;
}
#quadrant-not-urgent-important {
background-color: #90c695;
border-color: #7bb382;
color: white;
}
#quadrant-urgent-not-important {
background-color: #e8b085;
border-color: #e09c6f;
color: white;
}
#quadrant-not-urgent-not-important {
background-color: #c29d9d;
border-color: #b38888;
color: white;
}
/* Quadrant header styles */
.quadrant h3 {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
cursor: help;
position: relative;
}
.quadrant h3 .material-icons {
font-size: 20px;
opacity: 0.9;
}
/* Tooltip styles */
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: normal;
line-height: 1.4;
max-width: 250px;
z-index: 1000;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
}
.tooltip.show {
opacity: 1;
}
.tooltip.show::before {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
}
/* Arrow for tooltip positioned below header (top quadrants) */
.tooltip.show.below::before {
top: -12px;
border-bottom-color: rgba(0, 0, 0, 0.9);
}
/* Arrow for tooltip positioned above header (bottom quadrants) */
.tooltip.show.above::before {
bottom: -12px;
border-top-color: rgba(0, 0, 0, 0.9);
}
/* Drag and drop styles */
</style>
<script>(() => {
// node-modules-polyfills:node:process
function unimplemented(name) {
throw new Error("Node.js process " + name + " is not supported by JSPM core outside of Node.js");
}
var queue = [], draining = !1, currentQueue, queueIndex = -1;
function cleanUpNextTick() {
!draining || !currentQueue || (draining = !1, currentQueue.length ? queue = currentQueue.concat(queue) : queueIndex = -1, queue.length && drainQueue());
}
function drainQueue() {
if (!draining) {
var timeout = setTimeout(cleanUpNextTick, 0);
draining = !0;
for (var len = queue.length; len; ) {
for (currentQueue = queue, queue = []; ++queueIndex < len; )
currentQueue && currentQueue[queueIndex].run();
queueIndex = -1, len = queue.length;
}
currentQueue = null, draining = !1, clearTimeout(timeout);
}
}
function nextTick(fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1)
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
queue.push(new Item(fun, args)), queue.length === 1 && !draining && setTimeout(drainQueue, 0);
}
function Item(fun, array) {
this.fun = fun, this.array = array;
}
Item.prototype.run = function() {
this.fun.apply(null, this.array);
};
var title = "browser", arch = "x64", platform = "browser", env = {
PATH: "/usr/bin",
LANG: typeof navigator != "undefined" ? navigator.language + ".UTF-8" : void 0,
PWD: "/",
HOME: "/home",
TMP: "/tmp"
}, argv = ["/usr/bin/node"], execArgv = [], version = "v16.8.0", versions = {}, emitWarning = function(message, type) {
console.warn((type ? type + ": " : "") + message);
}, binding = function(name) {
unimplemented("binding");
}, umask = function(mask) {
return 0;
}, cwd = function() {
return "/";
}, chdir = function(dir) {
}, release = {
name: "node",
sourceUrl: "",
headersUrl: "",
libUrl: ""
};
function noop() {
}
var browser = !0, _rawDebug = noop, moduleLoadList = [];
function _linkedBinding(name) {
unimplemented("_linkedBinding");
}
var domain = {}, _exiting = !1, config = {};
function dlopen(name) {
unimplemented("dlopen");
}
function _getActiveRequests() {
return [];
}
function _getActiveHandles() {
return [];
}
var reallyExit = noop, _kill = noop, cpuUsage = function() {
return {};
}, resourceUsage = cpuUsage, memoryUsage = cpuUsage, kill = noop, exit = noop, openStdin = noop, allowedNodeEnvironmentFlags = {};
function assert(condition, message) {
if (!condition) throw new Error(message || "assertion error");
}
var features = {
inspector: !1,
debug: !1,
uv: !1,
ipv6: !1,
tls_alpn: !1,
tls_sni: !1,
tls_ocsp: !1,
tls: !1,
cached_builtins: !0
}, _fatalExceptions = noop, setUncaughtExceptionCaptureCallback = noop;
function hasUncaughtExceptionCaptureCallback() {
return !1;
}
var _tickCallback = noop, _debugProcess = noop, _debugEnd = noop, _startProfilerIdleNotifier = noop, _stopProfilerIdleNotifier = noop, stdout = void 0, stderr = void 0, stdin = void 0, abort = noop, pid = 2, ppid = 1, execPath = "/bin/usr/node", debugPort = 9229, argv0 = "node", _preload_modules = [], setSourceMapsEnabled = noop, _performance = {
now: typeof performance != "undefined" ? performance.now.bind(performance) : void 0,
timing: typeof performance != "undefined" ? performance.timing : void 0
};
_performance.now === void 0 && (nowOffset = Date.now(), _performance.timing && _performance.timing.navigationStart && (nowOffset = _performance.timing.navigationStart), _performance.now = () => Date.now() - nowOffset);
var nowOffset;
function uptime() {
return _performance.now() / 1e3;
}
var nanoPerSec = 1e9;
function hrtime(previousTimestamp) {
var baseNow = Math.floor((Date.now() - _performance.now()) * 1e-3), clocktime = _performance.now() * 1e-3, seconds = Math.floor(clocktime) + baseNow, nanoseconds = Math.floor(clocktime % 1 * 1e9);
return previousTimestamp && (seconds = seconds - previousTimestamp[0], nanoseconds = nanoseconds - previousTimestamp[1], nanoseconds < 0 && (seconds--, nanoseconds += nanoPerSec)), [seconds, nanoseconds];
}
hrtime.bigint = function(time) {
var diff = hrtime(time);
return typeof BigInt == "undefined" ? diff[0] * nanoPerSec + diff[1] : BigInt(diff[0] * nanoPerSec) + BigInt(diff[1]);
};
var _maxListeners = 10, _events = {}, _eventsCount = 0;
function on() {
return process;
}
var addListener = on, once = on, off = on, removeListener = on, removeAllListeners = on, emit = noop, prependListener = on, prependOnceListener = on;
function listeners(name) {
return [];
}
var process = {
version,
versions,
arch,
platform,
browser,
release,
_rawDebug,
moduleLoadList,
binding,
_linkedBinding,
_events,
_eventsCount,
_maxListeners,
on,
addListener,
once,
off,
removeListener,
removeAllListeners,
emit,
prependListener,
prependOnceListener,
listeners,
domain,
_exiting,
config,
dlopen,
uptime,
_getActiveRequests,
_getActiveHandles,
reallyExit,
_kill,
cpuUsage,
resourceUsage,
memoryUsage,
kill,
exit,
openStdin,
allowedNodeEnvironmentFlags,
assert,
features,
_fatalExceptions,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback,
emitWarning,
nextTick,
_tickCallback,
_debugProcess,
_debugEnd,
_startProfilerIdleNotifier,
_stopProfilerIdleNotifier,
stdout,
stdin,
stderr,
abort,
umask,
chdir,
cwd,
env,
title,
argv,
execArgv,
pid,
ppid,
execPath,
debugPort,
hrtime,
argv0,
_preload_modules,
setSourceMapsEnabled
};
// plugin/src/graphConfig.js
var defaultConfig = {
style: [
{
selector: "node[label]",
style: {
label: "data(label)",
color: "#ffffff",
"text-valign": "center",
"text-halign": "center",
"font-size": "13px",
"font-weight": 500,
"text-wrap": "wrap",
"text-max-width": "180px",
"text-margin-y": 0
}
},
{
selector: "node",
style: {
"background-color": "#1f6feb",
// Default blue for unblocked tasks
width: "200px",
height: "60px",
shape: "round-rectangle",
"border-width": 1,
"border-color": "rgba(240,246,252,0.1)",
padding: "8px"
}
},
{
selector: ".blocked-node",
style: {
"background-color": "#21262d",
"border-color": "#30363d",
color: "#8b949e",
opacity: 0.8
}
},
{
selector: ".completed-node",
style: {
"background-color": "#238636",
"border-color": "#1a4721",
color: "#ffffff",
opacity: 0.9
}
},
{
selector: "edge",
style: {
width: 3,
"line-color": "#30363d",
"target-arrow-color": "#30363d",
"target-arrow-shape": "vee",
"curve-style": "bezier",
"arrow-scale": 1.5,
opacity: 0.8
}
},
{
selector: "edge.from-completed",
style: {
"line-color": "#238636",
"target-arrow-color": "#238636",
opacity: 0.6
}
}
],
layout: {
name: "dagre",
rankDir: "LR",
spacingFactor: 1.2
}
}, edgeHandlesConfig = {
canConnect: function(sourceNode, targetNode) {
if (sourceNode.same(targetNode) || sourceNode.cy().edges().filter(
(edge) => edge.source().id() === sourceNode.id() && edge.target().id() === targetNode.id() || edge.source().id() === targetNode.id() && edge.target().id() === sourceNode.id()
).length > 0)
return !1;
let visited = /* @__PURE__ */ new Set();
function hasPath(current, end) {
if (current.id() === end.id()) return !0;
visited.add(current.id());
let outgoers = current.outgoers().nodes().filter((n) => !visited.has(n.id()));
for (let node of outgoers)
if (hasPath(node, end)) return !0;
return !1;
}
return !hasPath(targetNode, sourceNode);
},
edgeParams: function(sourceNode, targetNode) {
return {
group: "edges",
data: { source: sourceNode.id(), target: targetNode.id() }
};
},
hoverDelay: 150,
snap: !0,
snapThreshold: 50,
snapFrequency: 15,
preview: !0,
handleNodes: "node",
handlePosition: function(node) {
return "middle top";
},
handleInDrawMode: !1,
loopAllowed: function(node) {
return !1;
},
nodeLoopOffset: -50,
edgeType: function(sourceNode, targetNode) {
return "flat";
}
}, edgeHandlesStyles = [
{
selector: ".eh-handle",
style: {
"background-color": "#da3633",
width: 12,
height: 12,
shape: "ellipse",
"overlay-opacity": 0,
"border-width": 2,
"border-color": "#ffffff"
}
},
{
selector: ".eh-hover",
style: {
"background-color": "#238636"
}
},
{
selector: ".eh-source",
style: {
"border-width": 2,
"border-color": "#1f6feb"
}
},
{
selector: ".eh-target",
style: {
"border-width": 2,
"border-color": "#238636"
}
},
{
selector: ".eh-preview, .eh-ghost-edge",
style: {
width: 2,
"line-color": "#da3633",
"target-arrow-color": "#da3633",
"target-arrow-shape": "vee"
}
}
];
// plugin/src/taskManager.js
var TaskManager = class {
constructor(cy, taskAPIService = null) {
this.cy = cy, this.taskAPIService = taskAPIService, this.autoSyncEnabled = !1, this.autoSyncInterval = null, this.lastSyncHash = null, this.lastTaskCount = 0, this.isSyncing = !1, this.SYNC_INTERVAL_MS = 1e3, this.MAX_SYNC_INTERVAL_MS = 3e4, this.currentSyncInterval = this.SYNC_INTERVAL_MS, this.savedViewport = null;
}
/**
* Creates a new task node
* @param {Object} taskData - Task data including content, uuid, etc.
* @returns {Object} The created node
*/
insertTask(taskData) {
let strippedContent = this.stripDependencyLinks(taskData.content), nodeLabel = this.createNodeLabel(strippedContent), node = this.cy.add({
group: "nodes",
data: {
// Spread all task properties to preserve custom fields like matrix properties
...taskData,
// Override with computed/required properties
id: taskData.uuid,
label: nodeLabel,
content: taskData.content,
completedAt: taskData.completedAt || null,
noteUUID: taskData.noteUUID,
score: taskData.score
}
}).nodes()[0];
return node.ungrabify(), this.updateDependencies(taskData), this.updateNodeStyles(), node;
}
/**
* Updates task dependencies based on content
* @param {Object} taskData - Task data containing content with dependency links
*/
updateDependencies(taskData) {
this.cy.edges().filter((edge) => edge.source().id() === taskData.uuid).remove();
let matches = [...taskData.content.matchAll(/\\[([\\w-]+)\\]\\(https:\\/\\/www\\.amplenote\\.com\\/notes\\/tasks\\/[^)]+\\?relation=blocking\\)/g)];
[...new Set(matches.map((match) => match[1]))].forEach((targetId) => {
if (this.cy.$id(targetId).length === 0) {
console.warn(\`Skipping edge creation: target task '\${targetId}' does not exist\`);
return;
}
this.wouldCreateCycle(taskData.uuid, targetId) || this.cy.add({
group: "edges",
data: {
id: \`\${taskData.uuid}-\${targetId}\`,
source: taskData.uuid,
// This task is the source (blocker)
target: targetId
// The referenced task is the target (blocked)
}
});
});
}
/**
* Checks if adding an edge would create a cycle
* @param {string} sourceId - Source node ID
* @param {string} targetId - Target node ID
* @returns {boolean} True if adding the edge would create a cycle
*/
wouldCreateCycle(sourceId, targetId) {
let visited = /* @__PURE__ */ new Set(), target = this.cy.$id(targetId), hasPath = (current, end) => {
if (current.id() === end) return !0;
visited.add(current.id());
let outgoers = current.outgoers().nodes().filter((n) => !visited.has(n.id()));
for (let node of outgoers)
if (hasPath(node, end)) return !0;
return !1;
};
return hasPath(target, sourceId);
}
/**
* Strips dependency links from content
* @param {string} content - Content with possible dependency links
* @returns {string} Content with dependency links removed
*/
stripDependencyLinks(content) {
return content.replace(/\\[[^\\]]+\\]\\([^)]*\\?relation=(?:blocking|mirrored)[^)]*\\)/g, "").trim();
}
/**
* Processes task content by removing markup and cleaning formatting
* @param {string} content - Task content (should be already stripped of dependency links)
* @param {boolean} preserveMultiline - Whether to preserve multiple lines (default: false)
* @returns {string} Processed content without markup
*/
processTaskContent(content, preserveMultiline = !1) {
let withoutMarkTags = content.replace(/<\\/?mark[^>]*>/g, "").replace(/<!--[^]*?-->/g, ""), lines = withoutMarkTags.split(\`
\`), processedContent;
return lines.length > 1 && lines[0].endsWith("\\\\") ? processedContent = lines.join(" ").replace(/\\\\/g, "").trim() : preserveMultiline ? processedContent = withoutMarkTags : processedContent = lines[0], processedContent.replace(/\\\\/g, "").replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, "$1").trim();
}
/**
* Creates a clean label for node display
* @param {string} content - Task content (should be already stripped of dependency links)
* @returns {string} Clean label for node display (first line, no \\, max 80 chars)
*/
createNodeLabel(content) {
let processedContent = this.processTaskContent(content, !1);
return processedContent.length <= 80 ? processedContent : processedContent.substring(0, 77) + "...";
}
/**
* Completes a task
* @param {string} taskId - ID of the task to complete
*/
completeTask(taskId) {
let node = this.cy.$id(taskId);
node && (node.data("completedAt", Math.floor(Date.now() / 1e3)), this.updateNodeStyles());
}
/**
* Reopens a task and recursively reopens all completed dependent tasks
* @param {string} taskId - ID of the task to reopen
*/
async reopenTask(taskId) {
let node = this.cy.$id(taskId);
if (!node || node.length === 0) {
console.warn(\`Task \${taskId} not found for reopening\`);
return;
}
let tasksToReopen = this.getCompletedDependentsRecursive(node);
if (node.data("completedAt") && tasksToReopen.unshift(node), console.log(\`Cascading reopen: reopening \${tasksToReopen.length} completed tasks\`), tasksToReopen.forEach((taskNode) => {
taskNode.data("completedAt", null);
}), this.updateNodeStyles(), this.taskAPIService)
for (let taskNode of tasksToReopen)
try {
let updatedTaskData = {
content: taskNode.data("content"),
completedAt: null,
noteUUID: taskNode.data("noteUUID"),
score: taskNode.data("score")
};
console.log(\`Persisting reopen for task \${taskNode.id()}\`), await this.taskAPIService.updateTask(taskNode.id(), updatedTaskData);
} catch (error) {
console.error(\`Failed to persist reopening for task \${taskNode.id()}:\`, error);
}
}
/**
* Gets all completed dependent tasks recursively
* @param {Object} node - The node to get completed dependents for
* @returns {Array} Array of completed dependent nodes
*/
getCompletedDependentsRecursive(node) {
let completedDependents = [], visited = /* @__PURE__ */ new Set(), processed = /* @__PURE__ */ new Set(), startNodeId = node.id(), traverseCompletedDependents = (currentNode) => {
if (visited.has(currentNode.id()))
return;
visited.add(currentNode.id()), currentNode.outgoers("edge").targets().forEach((dependent) => {
dependent.data("completedAt") && !processed.has(dependent.id()) && dependent.id() !== startNodeId && (completedDependents.push(dependent), processed.add(dependent.id()), traverseCompletedDependents(dependent));
});
};
return traverseCompletedDependents(node), completedDependents;
}
/**
* Gets all dependent tasks (keeping original method for backward compatibility)
* @param {Object} node - The node to get dependents for
* @returns {Array} Array of dependent nodes
*/
getAllDependents(node) {
let dependents = /* @__PURE__ */ new Set(), traverse = (current) => {
current.outgoers().nodes().forEach((n) => {
dependents.has(n) || (dependents.add(n), traverse(n));
});
};
return traverse(node), Array.from(dependents);
}
/**
* Checks if a node is blocked by incomplete dependencies
* @param {Object} node - The node to check
* @returns {boolean} True if the node is blocked
*/
isBlocked(node) {
return node.incomers("edge").sources().some((blocker) => !blocker.data("completedAt"));
}
/**
* Creates a blocker dependency with automatic reopening of completed targets
* @param {string} sourceTaskId - ID of the task that will block
* @param {string} targetTaskId - ID of the task that will be blocked
* @returns {boolean} True if dependency was created successfully
*/
async createBlockerDependency(sourceTaskId, targetTaskId) {
if (this.wouldCreateCycle(sourceTaskId, targetTaskId))
return !1;
let targetNode = this.cy.$id(targetTaskId);
if (targetNode.length === 0)
return console.warn(\`Target node \${targetTaskId} does not exist\`), !1;
if (targetNode.data("completedAt")) {
console.log(\`Target task \${targetTaskId} is completed - reopening it because we're adding a blocker\`);
let tasksToReopen = this.getCompletedDependentsRecursive(targetNode);
if (targetNode.data("completedAt") && tasksToReopen.unshift(targetNode), tasksToReopen.forEach((taskNode) => {
taskNode.data("completedAt", null);
}), this.updateNodeStyles(), this.taskAPIService && tasksToReopen.length > 0)
try {
for (let taskNode of tasksToReopen) {
let updatedTaskData = {
content: taskNode.data("content"),
completedAt: null,
noteUUID: taskNode.data("noteUUID"),
score: taskNode.data("score")
};
await this.taskAPIService.updateTask(taskNode.id(), updatedTaskData);
}
} catch (error) {
console.error(\`Failed to cascade reopen for task \${targetTaskId}:\`, error);
}
}
let sourceTask = this.getTasks().find((t) => t.uuid === sourceTaskId);
if (sourceTask) {
let newLink = \`[\${targetTaskId}](https://www.amplenote.com/notes/tasks/\${targetTaskId}?relation=blocking)\`, newContent = sourceTask.content + " " + newLink;
await this.updateTask(sourceTaskId, { content: newContent.trim() });
}
return !0;
}
/**
* Updates visual styles for all nodes and edges
*/
updateNodeStyles() {
this.cy.edges().removeClass("from-completed"), this.cy.nodes().forEach((node) => {
node.removeClass("completed-node blocked-node"), node.data("completedAt") ? (node.addClass("completed-node"), node.outgoers("edge").addClass("from-completed")) : this.isBlocked(node) && node.addClass("blocked-node");
});
}
/**
* Gets all tasks as an array
* @returns {Array} Array of task objects
*/
getTasks() {
return this.cy.nodes().map((node) => ({
...node.data(),
uuid: node.id()
// Ensure uuid is set correctly from id
}));
}
/**
* Validates if the proposed content update would create valid dependencies
* @param {string} uuid - Task UUID
* @param {string} content - Proposed content
* @returns {Object} Validation result with {valid: boolean, reason: string}
*/
validateDependencies(uuid, content) {
let matches = [...content.matchAll(/\\[([\\w-]+)\\]\\(https:\\/\\/www\\.amplenote\\.com\\/notes\\/tasks\\/[^)]+\\?relation=blocking\\)/g)];
for (let match of matches) {
let targetId = match[1];
if (targetId === uuid)
return { valid: !1, reason: "Self-loops are not allowed" };
if (this.cy.$id(targetId).length > 0) {
if (this.cy.$id(uuid).length > 0) {
if (this.wouldCreateCycle(uuid, targetId))
return { valid: !1, reason: "Adding dependency would create a cycle" };
} else if (this.wouldCreateCycleWithNewNode(uuid, targetId, matches))
return { valid: !1, reason: "Adding dependency would create a cycle" };
}
}
return { valid: !0 };
}
/**
* Checks if adding a new node with the given dependencies would create a cycle
* @param {string} newNodeId - ID of the new node to be added
* @param {string} currentTargetId - Current dependency target being checked
* @param {Array} allMatches - All dependency matches from the new node's content
* @returns {boolean} True if adding these dependencies would create a cycle
*/
wouldCreateCycleWithNewNode(newNodeId, currentTargetId, allMatches) {
let allTargetIds = allMatches.map((match) => match[1]), visited = /* @__PURE__ */ new Set(), hasPathToAnyTarget = (startNodeId, targetIds) => {
if (targetIds.includes(startNodeId)) return !0;
if (visited.has(startNodeId)) return !1;
visited.add(startNodeId);
let startNode = this.cy.$id(startNodeId);
if (startNode.length === 0) return !1;
let outgoers = startNode.outgoers().nodes();
for (let node of outgoers)
if (hasPathToAnyTarget(node.id(), targetIds))
return !0;
return !1;
};
return hasPathToAnyTarget(currentTargetId, allTargetIds);
}
/**
* Updates a task with new data
* @param {string} uuid - Task UUID
* @param {Object} updates - Updates to apply to the task
*/
async updateTask(uuid, updates) {
let node = this.cy.$id(uuid);
if (node.length === 0)
return !1;
try {
if (updates.content !== void 0) {
let validation = this.validateDependencies(uuid, updates.content);
if (!validation.valid)
throw new Error(\`Cannot update task content: \${validation.reason}\`);
let strippedContent = this.stripDependencyLinks(updates.content), nodeLabel = this.createNodeLabel(strippedContent);
node.data("label", nodeLabel), node.data("content", updates.content), this.updateDependencies({ uuid, content: updates.content });
}
if (Object.keys(updates).forEach((key) => {
key !== "content" && node.data(key, updates[key]);
}), this.updateNodeStyles(), this.taskAPIService) {
let allNodeData = node.data(), updatedTaskData = {
content: allNodeData.content,
completedAt: allNodeData.completedAt,
noteUUID: allNodeData.noteUUID,
score: allNodeData.score
};
if (Object.keys(updates).forEach((key) => {
["content", "completedAt", "noteUUID", "score"].includes(key) || (updatedTaskData[key] = allNodeData[key]);
}), await this.taskAPIService.updateTask(uuid, updatedTaskData) === !1)
return !1;
}
return !0;
} catch (error) {
throw console.error("Failed to update task:", error), error;
}
}
/**
* Sets all tasks (replaces existing tasks)
* @param {Array} tasks - Array of task objects
*/
setTasks(tasks) {
this.cy.elements().remove(), tasks.forEach((task) => {
let strippedContent = this.stripDependencyLinks(task.content), nodeLabel = this.createNodeLabel(strippedContent);
this.cy.add({
group: "nodes",
data: {
// Spread all task properties to preserve custom fields like matrix properties
...task,
// Override with computed/required properties
id: task.uuid,
label: nodeLabel,
content: task.content,
completedAt: task.completedAt || null,
noteUUID: task.noteUUID,
score: task.score
}
});
}), tasks.forEach((task) => {
this.updateDependencies(task);
}), this.cy.nodes().ungrabify(), this.updateNodeStyles(), this.runLayoutWithViewportPersistence(), this.lastTaskCount = tasks.length, this.lastSyncHash = this._generateTasksHash(tasks);
}
/**
* Adds a new task to the graph
* @param {Object} taskData - Task data including content, uuid, etc.
* @returns {Object} The created node
*/
addTask(taskData) {
if (!taskData.uuid || !taskData.content)
throw new Error("Task must have uuid and content fields");
if (this.cy.$id(taskData.uuid).length > 0)
throw new Error(\`Task with UUID \${taskData.uuid} already exists\`);
let validation = this.validateDependencies(taskData.uuid, taskData.content);
if (!validation.valid)
throw new Error(\`Cannot update task content: \${validation.reason}\`);
let completeTaskData = {
// Default values
completedAt: null,
noteUUID: "note-1",
score: 1,
// Spread all task properties to preserve custom fields like matrix properties
...taskData,
// Ensure required fields are set
uuid: taskData.uuid,
content: taskData.content
}, node = this.insertTask(completeTaskData);
return this.cy.trigger("add", "node"), node;
}
/**
* Generates a unique task ID
* @returns {string} Unique task ID
*/
generateTaskId() {
return "task-" + Date.now() + "-" + Math.random().toString(36).substr(2, 9);
}
/**
* Starts automatic synchronization with the note content
* @param {string} noteUUID - The note UUID to sync with
*/
startAutoSync(noteUUID) {
if (this.autoSyncEnabled) {
console.log("Auto-sync already enabled");
return;
}
this.autoSyncEnabled = !0, this.noteUUID = noteUUID, this.currentSyncInterval = this.SYNC_INTERVAL_MS, console.log(\`Starting auto-sync for note \${noteUUID} every \${this.SYNC_INTERVAL_MS}ms\`), this._scheduleNextSync();
}
/**
* Stops automatic synchronization
*/
stopAutoSync() {
this.autoSyncEnabled && (this.autoSyncEnabled = !1, this.autoSyncInterval && (clearTimeout(this.autoSyncInterval), this.autoSyncInterval = null), console.log("Auto-sync stopped"));
}
/**
* Schedules the next sync operation
*/
_scheduleNextSync() {
this.autoSyncEnabled && (this.autoSyncInterval = setTimeout(async () => {
try {
await this._performAutoSync(), this.currentSyncInterval = this.SYNC_INTERVAL_MS;
} catch (error) {
console.error("Auto-sync failed:", error), this.currentSyncInterval = Math.min(
this.currentSyncInterval * 2,
this.MAX_SYNC_INTERVAL_MS
), console.log(\`Auto-sync backing off to \${this.currentSyncInterval}ms\`);
}
this._scheduleNextSync();
}, this.currentSyncInterval));
}
/**
* Performs the actual sync operation with smart change detection
*/
async _performAutoSync() {
if (!(this.isSyncing || !this.taskAPIService || !this.noteUUID)) {
this.isSyncing = !0;
try {
let freshTasks = await this.taskAPIService.getNoteTasks(this.noteUUID, { includeDone: !0 });
if (freshTasks.length !== this.lastTaskCount) {
console.log(\`Task count changed: \${this.lastTaskCount} \\u2192 \${freshTasks.length}\`), await this._performIncrementalUpdate(freshTasks), this.lastTaskCount = freshTasks.length;
return;
}
let currentHash = this._generateTasksHash(freshTasks);
currentHash !== this.lastSyncHash && (console.log("Task content changed, performing incremental update"), await this._performIncrementalUpdate(freshTasks), this.lastSyncHash = currentHash);
} finally {
this.isSyncing = !1;
}
}
}
/**
* Generates a hash of the tasks for change detection
* @param {Array} tasks - Array of task objects
* @returns {string} Hash representing the current state
*/
_generateTasksHash(tasks) {
let relevantData = tasks.map((task) => ({
uuid: task.uuid,
content: task.content,
completedAt: task.completedAt,
score: task.score,
important: task.important,
urgent: task.urgent
}));
return relevantData.sort((a, b) => a.uuid.localeCompare(b.uuid)), JSON.stringify(relevantData);
}
/**
* Performs incremental update instead of full rebuild
* @param {Array} freshTasks - Fresh tasks from the API
*/
async _performIncrementalUpdate(freshTasks) {
let currentTasks = this.getTasks(), currentTaskMap = new Map(currentTasks.map((task) => [task.uuid, task])), freshTaskMap = new Map(freshTasks.map((task) => [task.uuid, task])), hasChanges = !1, changes = {
added: [],
removed: [],
updated: []
};
for (let currentTask of currentTasks)
if (!freshTaskMap.has(currentTask.uuid)) {
let node = this.cy.$id(currentTask.uuid);
node.length > 0 && (node.remove(), changes.removed.push(currentTask.uuid), hasChanges = !0);
}
for (let freshTask of freshTasks)
currentTaskMap.has(freshTask.uuid) || (this.insertTask(freshTask), changes.added.push(freshTask.uuid), hasChanges = !0);
for (let freshTask of freshTasks) {
let currentTask = currentTaskMap.get(freshTask.uuid);
currentTask && this._hasTaskChanged(currentTask, freshTask) && (await this._updateSingleTaskIncremental(freshTask.uuid, currentTask, freshTask), changes.updated.push(freshTask.uuid), hasChanges = !0);
}
hasChanges && (this.cy.edges().remove(), freshTasks.forEach((task) => {
this.updateDependencies(task);
}), this.updateNodeStyles(), (changes.added.length > 0 || changes.removed.length > 0 || changes.updated.some((uuid) => this._contentStructurallyChanged(currentTaskMap.get(uuid), freshTaskMap.get(uuid)))) && this.runLayoutWithViewportPersistence(), console.log("Incremental update completed:", changes));
}
/**
* Checks if a task has changed in ways that matter for visualization
* @param {Object} currentTask - Current task data
* @param {Object} freshTask - Fresh task data from API
* @returns {boolean} True if the task has changed
*/
_hasTaskChanged(currentTask, freshTask) {
return currentTask.content !== freshTask.content || currentTask.completedAt !== freshTask.completedAt || currentTask.score !== freshTask.score || currentTask.important !== freshTask.important || currentTask.urgent !== freshTask.urgent;
}
/**
* Checks if content changes affect graph structure (dependencies)
* @param {Object} currentTask - Current task data
* @param {Object} freshTask - Fresh task data
* @returns {boolean} True if dependencies changed
*/
_contentStructurallyChanged(currentTask, freshTask) {
let currentDeps = this._extractDependencies(currentTask.content), freshDeps = this._extractDependencies(freshTask.content);
return JSON.stringify(currentDeps) !== JSON.stringify(freshDeps);
}
/**
* Extracts dependency UUIDs from content
* @param {string} content - Task content
* @returns {Array} Array of dependency UUIDs
*/
_extractDependencies(content) {
return [...content.matchAll(/\\[([\\w-]+)\\]\\(https:\\/\\/www\\.amplenote\\.com\\/notes\\/tasks\\/[^)]+\\?relation=blocking\\)/g)].map((match) => match[1]).sort();
}
/**
* Updates a single task incrementally without rebuilding the graph
* @param {string} uuid - Task UUID
* @param {Object} currentTask - Current task data
* @param {Object} freshTask - Fresh task data
*/
async _updateSingleTaskIncremental(uuid, currentTask, freshTask) {
let node = this.cy.$id(uuid);
if (!(!node || node.length === 0)) {
if (freshTask.content !== currentTask.content) {
let strippedContent = this.stripDependencyLinks(freshTask.content), nodeLabel = this.createNodeLabel(strippedContent);
node.data("label", nodeLabel), node.data("content", freshTask.content);
}
freshTask.completedAt !== currentTask.completedAt && node.data("completedAt", freshTask.completedAt), freshTask.score !== currentTask.score && node.data("score", freshTask.score), freshTask.important !== currentTask.important && node.data("important", freshTask.important), freshTask.urgent !== currentTask.urgent && node.data("urgent", freshTask.urgent);
}
}
/**
* Gets the current auto-sync status
* @returns {Object} Auto-sync status information
*/
getAutoSyncStatus() {
return {
enabled: this.autoSyncEnabled,
interval: this.currentSyncInterval,
noteUUID: this.noteUUID,
lastSyncHash: this.lastSyncHash,
isSyncing: this.isSyncing
};
}
/**
* Sets a custom sync interval
* @param {number} intervalMs - Sync interval in milliseconds
*/
setAutoSyncInterval(intervalMs) {
if (intervalMs < 1e3) {
console.warn("Auto-sync interval too low, minimum is 1000ms");
return;
}
this.SYNC_INTERVAL_MS = intervalMs, this.currentSyncInterval = intervalMs, console.log(\`Auto-sync interval updated to \${intervalMs}ms\`), this.autoSyncEnabled && (this.stopAutoSync(), this.startAutoSync(this.noteUUID));
}
/**
* Performs a manual sync (useful for forcing immediate update)
* @returns {Promise} Promise that resolves when sync is complete
*/
async performManualSync() {
if (!this.taskAPIService || !this.noteUUID)
throw new Error("Auto-sync not configured");
console.log("Performing manual sync..."), await this._performAutoSync(), console.log("Manual sync completed");
}
/**
* Saves the current viewport state (zoom and pan)
*/
saveViewport() {
this.savedViewport = {
zoom: this.cy.zoom(),
pan: this.cy.pan()
};
}
/**
* Restores the saved viewport state (zoom and pan)
*/
restoreViewport() {
this.savedViewport && this.cy.viewport({
zoom: this.savedViewport.zoom,
pan: this.savedViewport.pan
});
}
/**
* Runs a layout while preserving the current viewport
* @param {Object} layoutOptions - Cytoscape layout options
*/
runLayoutWithViewportPersistence(layoutOptions = {}) {
this.saveViewport();
let layout = this.cy.layout({
name: "dagre",
rankDir: "LR",
spacingFactor: 1.2,
animate: !1,
...layoutOptions
});
layout.on("layoutstop", () => {
setTimeout(() => {
this.restoreViewport();
}, 10);
}), layout.run();
}
/**
* Searches for nodes by text content in their labels and content
* @param {string} searchText - Text to search for (case-insensitive)
* @returns {Array} Array of matching nodes
*/
searchNodes(searchText) {
if (!searchText || searchText.trim() === "")
return [];
let searchLower = searchText.toLowerCase().trim();
return this.cy.nodes().filter((node) => {
let label = node.data("label") || "", content = node.data("content") || "";
return label.toLowerCase().includes(searchLower) || content.toLowerCase().includes(searchLower);
});
}
/**
* Gets all search matches with their labels for display
* @param {string} searchText - Text to search for
* @returns {Array} Array of {node, label, content, id} objects
*/
getSearchResults(searchText) {
return this.searchNodes(searchText).map((node) => ({
node,
label: node.data("label"),
content: node.data("content"),
id: node.id()
}));
}
/**
* Clean up resources when the TaskManager is destroyed
*/
destroy() {
this.stopAutoSync(), this.cy && this.cy.destroy();
}
};
// package.json
var package_default = {
name: "skilltrees",
version: "1.0.0",
description: "Skill tree visualization tests",
main: "src/index.js",
type: "module",
keywords: [],
author: "",
license: "ISC",
dependencies: {
canvas: "^3.1.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-dagre": "^2.5.0",
"cytoscape-edgehandles": "^4.0.1",
lodash: "^4.17.21"
},
devDependencies: {
"@babel/core": "^7.24.3",
"@babel/preset-env": "^7.24.3",
"babel-jest": "^29.7.0",
cors: "^2.8.5",
cytoscape: "^3.32.0",
esbuild: "0.25.3",
"esbuild-plugins-node-modules-polyfill": "1.7.0",
express: "^5.0.1",
jest: "^29.7.0",
"jest-environment-jsdom": "^30.0.0-beta.3",
jsdom: "^26.1.0",
"lodash-es": "^4.17.21",
"text-encoding": "^0.7.0"
},
scripts: {
test: "jest --testPathPattern=plugin",
"test:watch": "jest --testPathPattern=plugin --watch",
"build:prod": "NODE_ENV=production node esbuild.js $(pwd)/plugin",
"build:dev": "node esbuild.js $(pwd)/plugin --watch --server"
}
};
// common-utils/dynamic-import-esm.js
var dynamicImportESM = async (pkg, pkgVersion = null) => {
let cdnList = ["https://esm.sh/", "https://legacy.esm.sh/", "https://esm.run/"], resolvedVersion = resolvePackageVersion(pkg, pkgVersion), importCompleted = !1, importPromises = cdnList.map(async (cdn) => {
let url = buildCDNUrl(cdn, pkg, resolvedVersion);
if (cdn !== "https://esm.sh/" && await new Promise((resolve) => setTimeout(resolve, 600)), importCompleted)
throw new Error(\`Terminating as \${pkg} has already been imported\`);
return import(url).then((module) => ({ module, url })).catch((e) => {
throw console.warn(\`Failed to import \${pkg} from \${cdn}: \${e.message}\`), e;
});
});
try {
let result = await Promise.any(importPromises);
return importCompleted = !0, console.log(\`Imported \${pkg}@\${resolvedVersion} from \${result.url}\`), result.module;
} catch {
throw new Error(\`Failed to import \${pkg} from all available CDNs\`);
}
throw new Error(\`Failed to import \${pkg} from all available CDNs\`);
};
function getBasePackage(pkg) {
if (pkg.startsWith("@")) {
let [scope, name] = pkg.split("/");
return \`\${scope}/\${name}\`;
}
return pkg.split("/")[0];
}
function resolvePackageVersion(pkg, pkgVersion) {
let basePkg = getBasePackage(pkg), version2 = pkgVersion || package_default.dependencies[basePkg] || package_default.devDependencies[basePkg] || "latest";
return version2.startsWith("^") || version2.startsWith("~") ? version2.substring(1) : version2;
}
function getPackageFolderString(pkg) {
let folders = [];
return pkg.startsWith("@") ? [, , ...folders] = pkg.split("/") : [, ...folders] = pkg.split("/"), folders && folders.length > 0 ? \`/\${folders.join("/")}\` : "";
}
function buildCDNUrl(cdn, pkg, version2) {
let basePkg = getBasePackage(pkg), versionString = version2 !== "latest" ? \`@\${version2}\` : "", folderString = getPackageFolderString(pkg), url = new URL(\`\${cdn}\${basePkg}\${versionString}\${folderString}\`);
if (cdn !== "https://esm.sh/" && (basePkg.includes("react") || basePkg.includes("radix")))
throw new Error(\`React based packages is not supported in \${cdn}\`);
if (cdn !== "https://legacy.esm.sh/" && basePkg.includes("build"))
throw new Error(\`Build API package is not supported in \${cdn}\`);
return cdn === "https://esm.sh/" && (basePkg !== "react-dom" && basePkg !== "react" && url.searchParams.set("bundle-deps", !1), !pkg.endsWith(".css") && pkg !== "build" && url.searchParams.set("deps", \`react@\${package_default.dependencies.react},react-dom@\${package_default.dependencies["react-dom"]}\`)), url.toString();
}
var dynamic_import_esm_default = dynamicImportESM;
// common-utils/embed-comunication.js
function createCallAmplenotePluginMock(embedCommandsMock) {
return async (commandName, ...args) => {
if (commandName in embedCommandsMock)
return await embedCommandsMock[commandName](...args);
throw new Error(\`Unknown command: \${commandName}\`);
};
}
function deserializeWithFunctions(str) {
return JSON.parse(str, (key, value) => {
if (typeof value == "string" && value.startsWith("__FUNCTION:")) {
let functionBody = value.slice(11);
return new Function(\`return \${functionBody}\`)();
}
return value;
});
}
// common-utils/task-api-service.js
var TaskAPIService = class {
async getNoteTasks(noteUUID) {
throw new Error("getNoteTasks must be implemented");
}
async updateTask(taskUUID, updates) {
throw new Error("updateTask must be implemented");
}
async insertTask(noteUUID, taskData) {
throw new Error("insertTask must be implemented");
}
async deleteTask(taskUUID) {
throw new Error("deleteTask must be implemented");
}
}, DevTaskAPIService = class extends TaskAPIService {
constructor() {
super(), this.mockTasks = [
{
uuid: "task-1",
content: "JavaScript Fundamentals",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-2",
content: "Advanced JavaScript [task-1](https://www.amplenote.com/notes/tasks/task-1?relation=blocking)",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-3",
content: "Design user interface mockups",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-4",
content: "Write unit tests for authentication",
completedAt: null,
noteUUID: "note-1",
score: 3
},
{
uuid: "task-5",
content: "Review code with team",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-6",
content: "Deploy to staging environment",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-7",
content: "Update documentation",
completedAt: Math.floor(Date.now() / 1e3),
noteUUID: "note-1",
score: 1
},
{
uuid: "task-8",
content: "Fix critical bug in payment flow",
completedAt: null,
noteUUID: "note-1",
score: 5
},
{
uuid: "task-9",
content: "Optimize database queries",
completedAt: null,
noteUUID: "note-1",
score: 3
},
{
uuid: "task-10",
content: "Attend team standup meeting",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-11",
content: "Research new frontend framework",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-12",
content: "Implement user feedback form",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-13",
content: "Schedule performance review",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-14",
content: "Refactor legacy code module",
completedAt: null,
noteUUID: "note-1",
score: 4
},
{
uuid: "task-15",
content: "Set up automated testing pipeline",
completedAt: null,
noteUUID: "note-1",
score: 3
},
{
uuid: "task-16",
content: "Answer customer support emails",
completedAt: null,
noteUUID: "note-1",
score: 1
},
{
uuid: "task-17",
content: "Plan quarterly roadmap",
completedAt: null,
noteUUID: "note-1",
score: 3
},
{
uuid: "task-18",
content: "Update security certificates",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-19",
content: "Create onboarding tutorial",
completedAt: null,
noteUUID: "note-1",
score: 2
},
{
uuid: "task-20",
content: "Backup production database",
completedAt: null,
noteUUID: "note-1",
score: 2
}
];
}
async getNoteTasks(noteUUID) {
return console.log("[DEV] Getting tasks for note:", noteUUID), [...this.mockTasks];
}
async updateTask(taskUUID, updates) {
console.log("[DEV] Updating task:", taskUUID, updates);
let taskIndex = this.mockTasks.findIndex((t) => t.uuid === taskUUID);
return taskIndex !== -1 ? (this.mockTasks[taskIndex] = { ...this.mockTasks[taskIndex], ...updates }, !0) : !1;
}
async insertTask(noteUUID, taskData) {
console.log("[DEV] Inserting task into note:", noteUUID, taskData);
let taskUUID = \`task-\${Date.now()}\`, newTask = {
uuid: taskUUID,
completedAt: null,
score: 1,
noteUUID,
...taskData
};
return this.mockTasks.push(newTask), taskUUID;
}
async deleteTask(taskUUID) {
console.log("[DEV] Deleting task:", taskUUID);
let taskIndex = this.mockTasks.findIndex((t) => t.uuid === taskUUID);
return taskIndex !== -1 ? this.mockTasks.splice(taskIndex, 1)[0] : null;
}
}, TestTaskAPIService = class extends TaskAPIService {
constructor(initialMockData = []) {
super(), this.mockTasks = [...initialMockData], this.mockResponses = /* @__PURE__ */ new Map();
}
// Test helper methods
setMockTasks(tasks) {
this.mockTasks = [...tasks];
}
getMockTasks() {
return [...this.mockTasks];
}
setMockResponse(method, response) {
this.mockResponses.set(method, response);
}
clearMockResponses() {
this.mockResponses.clear();
}
reset() {
this.mockTasks = [], this.mockResponses.clear();
}
async getNoteTasks(noteUUID) {
return this.mockResponses.has("getNoteTasks") ? this.mockResponses.get("getNoteTasks") : this.mockTasks.filter((task) => task.noteUUID === noteUUID);
}
async updateTask(taskUUID, updates) {
if (this.mockResponses.has("updateTask"))
return this.mockResponses.get("updateTask");
let taskIndex = this.mockTasks.findIndex((t) => t.uuid === taskUUID);
return taskIndex !== -1 ? (this.mockTasks[taskIndex] = { ...this.mockTasks[taskIndex], ...updates }, !0) : !1;
}
async insertTask(noteUUID, taskData) {
if (this.mockResponses.has("insertTask"))
return this.mockResponses.get("insertTask");
let taskUUID = \`test-task-\${Date.now()}\`, newTask = {
uuid: taskUUID,
completedAt: null,
score: 1,
noteUUID,
...taskData
};
return this.mockTasks.push(newTask), taskUUID;
}
async deleteTask(taskUUID) {
if (this.mockResponses.has("deleteTask"))
return this.mockResponses.get("deleteTask");
let taskIndex = this.mockTasks.findIndex((t) => t.uuid === taskUUID);
return taskIndex !== -1 ? this.mockTasks.splice(taskIndex, 1)[0] : null;
}
}, ProdTaskAPIService = class extends TaskAPIService {
constructor(callAmplenotePlugin) {
if (super(), this.callAmplenotePlugin = callAmplenotePlugin, !callAmplenotePlugin)
throw new Error("ProdTaskAPIService requires callAmplenotePlugin function");
}
async getNoteTasks(noteUUID) {
try {
return await this.callAmplenotePlugin("getNoteTasks", { uuid: noteUUID }, { includeDone: !0 });
} catch (error) {
throw console.error("Failed to get tasks from Amplenote:", error), error;
}
}
async updateTask(taskUUID, updates) {
try {
return await this.callAmplenotePlugin("updateTask", taskUUID, updates);
} catch (error) {
throw console.error("Failed to update task in Amplenote:", error), error;
}
}
async deleteTask(taskUUID) {
try {
return await this.callAmplenotePlugin("deleteTask", taskUUID);
} catch (error) {
throw console.error("Failed to delete task in Amplenote:", error), error;
}
}
async insertTask(noteUUID, taskData) {
try {
return await this.callAmplenotePlugin("insertTask", { uuid: noteUUID }, taskData);
} catch (error) {
throw console.error("Failed to insert task in Amplenote:", error), error;
}
}
}, TaskAPIServiceFactory = class {
static create(environment2, options2 = {}) {
switch (environment2) {
case "development":
return new DevTaskAPIService();
case "test":
return new TestTaskAPIService(options2.mockData);
case "production":
if (!options2.callAmplenotePlugin)
throw new Error("Production environment requires callAmplenotePlugin option");
return new ProdTaskAPIService(options2.callAmplenotePlugin);
default:
throw new Error(\`Unknown environment: \${environment2}\`);
}
}
}, ServiceContainer = class {
constructor() {
this.services = /* @__PURE__ */ new Map(), this.singletons = /* @__PURE__ */ new Map();
}
register(name, factory, options2 = {}) {
this.services.set(name, { factory, options: options2 });
}
get(name) {
let service = this.services.get(name);
if (!service)
throw new Error(\`Service '\${name}' not found\`);
return service.options.singleton ? (this.singletons.has(name) || this.singletons.set(name, service.factory()), this.singletons.get(name)) : service.factory();
}
has(name) {
return this.services.has(name);
}
};
// common-utils/service-setup.js
function setupServices(environment2, options2 = {}) {
let container = new ServiceContainer();
switch (environment2) {
case "development":
container.register(
"taskAPI",
() => TaskAPIServiceFactory.create("development"),
{ singleton: !0 }
);
break;
case "test":
container.register(
"taskAPI",
() => TaskAPIServiceFactory.create("test", {
mockData: options2.mockData || []
})
);
break;
case "production":
let callAmplenotePlugin = options2.callAmplenotePlugin;
if (!callAmplenotePlugin)
if (window.INJECTED_EMBED_COMMANDS_MOCK)
callAmplenotePlugin = createCallAmplenotePluginMock(
deserializeWithFunctions(window.INJECTED_EMBED_COMMANDS_MOCK)
);
else if (window.callAmplenotePlugin)
callAmplenotePlugin = window.callAmplenotePlugin;
else
throw new Error("No callAmplenotePlugin available in production environment");
container.register(
"taskAPI",
() => TaskAPIServiceFactory.create("production", { callAmplenotePlugin }),
{ singleton: !0 }
);
break;
default:
throw new Error(\`Unknown environment: \${environment2}\`);
}
return container;
}
// plugin/embed/index.js
var environment = "production", options = {}, isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1", isFileProtocol = window.location.protocol === "file:", isDevPort = window.location.port === "3000" || window.location.port === "8080" || window.location.port === "5173";
console.log("Environment detection:", {
"process.env.NODE_ENV": typeof process != "undefined" ? "production" : "undefined",
hostname: window.location.hostname,
port: window.location.port,
protocol: window.location.protocol,
isLocalhost,
isFileProtocol,
isDevPort
});
typeof process != "undefined" && !1 || isLocalhost || isDevPort || isFileProtocol ? (environment = "development", console.log("\\u{1F680} Running in DEVELOPMENT mode")) : typeof window.INJECTED_CHART_DATA_MOCK != "undefined" ? (environment = "test", options.mockData = window.INJECTED_CHART_DATA_MOCK || [], console.log("\\u{1F9EA} Running in TEST mode")) : (environment = "production", console.log("\\u{1F310} Running in PRODUCTION mode"), window.INJECTED_EMBED_COMMANDS_MOCK && (window.callAmplenotePlugin = createCallAmplenotePluginMock(deserializeWithFunctions(window.INJECTED_EMBED_COMMANDS_MOCK))), window.appConnector = new Proxy({}, {
get: function(target, prop, receiver) {
return prop in target ? target[prop] : async function(...args) {
return await window.callAmplenotePlugin(prop, ...args);
};
}
}), options.callAmplenotePlugin = window.callAmplenotePlugin);
var serviceContainer = setupServices(environment, options);
window.appSettings = window.appSettings || {};
window.serviceContainer = serviceContainer;
function createCytoscape(container) {
let cy = cytoscape({
container,
...defaultConfig
}), eh = cy.edgehandles(edgeHandlesConfig);
return cy.style().append(edgeHandlesStyles), eh.enableDrawMode(), console.log("Edge handles initialized:", eh), cy.on("ehstart", (event, sourceNode) => {
console.log("Edge handle start:", sourceNode.id());
}), cy.on("ehpreview", (event, sourceNode, targetNode, previewEles) => {
console.log("Edge handle preview:", sourceNode.id(), "->", targetNode.id());
}), cy.on("ehstop", (event, sourceNode) => {
console.log("Edge handle stop:", sourceNode.id());
}), cy;
}
function initializeGraph(cy, taskAPIService) {
return new TaskManager(cy, taskAPIService);
}
console.log("Script loaded, checking document state");
async function initializeApp() {
console.log("Initializing app!"), console.log("Importing dependencies");
let cytoscape2 = (await dynamic_import_esm_default("cytoscape")).default, cytoscapeDagre = (await dynamic_import_esm_default("cytoscape-dagre")).default, cytoscapeEdgehandles = (await dynamic_import_esm_default("cytoscape-edgehandles")).default, lodash = (await dynamic_import_esm_default("lodash")).default;
window.cytoscape = cytoscape2, window._ = lodash, cytoscapeDagre(cytoscape2), cytoscapeEdgehandles(cytoscape2);
let cy = createCytoscape(document.getElementById("cy")), taskAPIService = serviceContainer.get("taskAPI"), taskManager = initializeGraph(cy, taskAPIService), textInputModal = document.getElementById("textInputModal"), modalPromptLabel = document.getElementById("modalPromptLabel"), modalInput = document.getElementById("modalInput"), modalSubmit = document.getElementById("modalSubmit"), modalCancel = document.getElementById("modalCancel"), currentModalCallback = null, nodeContextMenu = document.getElementById("node-context-menu"), edgeContextMenu = document.getElementById("edge-context-menu"), emptyContextMenu = document.getElementById("empty-context-menu"), completeTaskBtn = document.getElementById("complete-task"), reopenTaskBtn = document.getElementById("reopen-task"), removeDependencyBtn = document.getElementById("remove-dependency"), searchToBlockBtn = document.getElementById("search-to-block"), searchForBlockerBtn = document.getElementById("search-for-blocker"), redrawGraphBtn = document.getElementById("redraw-graph"), activeNode = null, activeEdge = null, searchModal = document.getElementById("search-modal"), searchModalTitle = document.getElementById("search-modal-title"), modalSearchInput = document.getElementById("modal-search-input"), modalSearchResults = document.getElementById("modal-search-results"), closeSearchModalBtn = document.getElementById("close-search-modal"), searchTimeout, selectedResultIndex = -1, searchMode = "block", instructionsToggle = document.getElementById("instructions-toggle"), instructionsPanel = document.getElementById("instructions-panel"), instructionsVisible = !1;
function showCustomPrompt(prompt, defaultValue, callback) {
modalPromptLabel.textContent = prompt, modalInput.value = defaultValue || "", textInputModal.style.display = "block", modalInput.focus(), currentModalCallback = callback;
}
modalSubmit.addEventListener("click", function() {
currentModalCallback && currentModalCallback(modalInput.value), textInputModal.style.display = "none";
}), modalCancel.addEventListener("click", function() {
textInputModal.style.display = "none";
}), cy.on("dblclick", "node", function(event) {
let node = event.target;
tooltipTimeout && (clearTimeout(tooltipTimeout), tooltipTimeout = null), nodeTooltip.style.display = "none", showCustomPrompt("Edit skill name:", node.data("content"), function(newContent) {
newContent && newContent.trim() !== "" && taskManager.updateTask(node.id(), { content: newContent.trim() });
});
}), cy.on("dblclick", function(event) {
if (event.target === cy) {
let position = event.position || event.renderedPosition;
showCustomPrompt("Enter skill name:", "", async function(newContent) {
if (newContent && newContent.trim() !== "")
try {
let noteUUID = window.noteUUID || "note-1", createdTask = {
uuid: await taskAPIService.insertTask(noteUUID, { content: newContent.trim() }),
content: newContent.trim(),
completedAt: null,
noteUUID,
score: 1
};
taskManager.addTask(createdTask), console.log("Created new task:", createdTask);
} catch (error) {
console.error("Failed to create task:", error), alert("Failed to create task. Please try again.");
}
});
}
}), cy.on("cxttap", "node", function(evt) {
evt.preventDefault(), activeNode = evt.target, activeEdge = null, tooltipTimeout && (clearTimeout(tooltipTimeout), tooltipTimeout = null), nodeTooltip.style.display = "none", edgeContextMenu.style.display = "none", emptyContextMenu.style.display = "none";
let isBlocked = taskManager.isBlocked(activeNode);
completeTaskBtn.style.display = activeNode.data("completedAt") || isBlocked ? "none" : "block", reopenTaskBtn.style.display = activeNode.data("completedAt") ? "block" : "none", nodeContextMenu.style.display = "block", nodeContextMenu.style.left = evt.renderedPosition.x + "px", nodeContextMenu.style.top = evt.renderedPosition.y + "px";
}), cy.on("cxttap", "edge", function(evt) {
evt.preventDefault(), activeEdge = evt.target, activeNode = null, nodeContextMenu.style.display = "none", emptyContextMenu.style.display = "none", edgeContextMenu.style.display = "block", edgeContextMenu.style.left = evt.renderedPosition.x + "px", edgeContextMenu.style.top = evt.renderedPosition.y + "px";
}), cy.on("cxttap", function(evt) {
evt.target === cy && !isEisenhowerMode && (evt.preventDefault(), activeNode = null, activeEdge = null, nodeContextMenu.style.display = "none", edgeContextMenu.style.display = "none", emptyContextMenu.style.display = "block", emptyContextMenu.style.left = evt.renderedPosition.x + "px", emptyContextMenu.style.top = evt.renderedPosition.y + "px");
}), cy.on("tap", function(evt) {
evt.target === cy && (nodeContextMenu.style.display = "none", edgeContextMenu.style.display = "none", emptyContextMenu.style.display = "none");
}), document.addEventListener("click", function() {
nodeContextMenu.style.display = "none", edgeContextMenu.style.display = "none", emptyContextMenu.style.display = "none";
}), completeTaskBtn.addEventListener("click", function() {
if (activeNode && !taskManager.isBlocked(activeNode)) {
let utcTimestamp = Math.floor(Date.now() / 1e3);
taskManager.updateTask(activeNode.id(), { completedAt: utcTimestamp });
}
}), reopenTaskBtn.addEventListener("click", function() {
activeNode && taskManager.reopenTask(activeNode.id());
}), removeDependencyBtn.addEventListener("click", function() {
if (activeEdge) {
let sourceTask = taskManager.getTasks().find((t) => t.uuid === activeEdge.source().id());
if (sourceTask) {
let targetId = activeEdge.target().id(), pattern = new RegExp(\`\\\\[\${targetId}\\\\]\\\\(https://www\\\\.amplenote\\\\.com/notes/tasks/\${targetId}\\\\?relation=blocking\\\\)\`), newContent = sourceTask.content.replace(pattern, "").trim();
taskManager.updateTask(sourceTask.uuid, { content: newContent });
}
}
}), instructionsToggle.addEventListener("click", function() {
instructionsVisible = !instructionsVisible, instructionsVisible ? instructionsPanel.classList.remove("hidden") : instructionsPanel.classList.add("hidden");
});
let nodeTooltip = document.getElementById("node-tooltip"), tooltipTimeout = null;
function getProcessedContent(fullContent) {
let withoutDependencyLinks = taskManager.stripDependencyLinks(fullContent);
return taskManager.processTaskContent(withoutDependencyLinks, !0);
}
cy.on("mouseover", "node", function(event) {
let fullContent = event.target.data("content");
tooltipTimeout && clearTimeout(tooltipTimeout), tooltipTimeout = setTimeout(() => {
let cleanContent = getProcessedContent(fullContent);
nodeTooltip.textContent = cleanContent, nodeTooltip.style.display = "block";
let renderedPosition = event.renderedPosition;
nodeTooltip.style.left = renderedPosition.x + 10 + "px", nodeTooltip.style.top = renderedPosition.y - 10 + "px";
}, 500);
}), cy.on("mouseout", "node", function(event) {
tooltipTimeout && (clearTimeout(tooltipTimeout), tooltipTimeout = null), nodeTooltip.style.display = "none";
}), cy.on("mousemove", function(event) {
if (nodeTooltip.style.display === "block") {
let renderedPosition = event.renderedPosition;
nodeTooltip.style.left = renderedPosition.x + 10 + "px", nodeTooltip.style.top = renderedPosition.y - 10 + "px";
}
});
function openSearchModal(mode = "block") {
searchMode = mode, mode === "blocker" ? (searchModalTitle.textContent = "Search for blocker task", modalSearchInput.placeholder = "Type to search for blocker task...") : (searchModalTitle.textContent = "Search for task to block", modalSearchInput.placeholder = "Type to search tasks..."), searchModal.style.display = "block", modalSearchInput.value = "", modalSearchResults.innerHTML = "", selectedResultIndex = -1, modalSearchInput.focus(), nodeContextMenu.style.display = "none", edgeContextMenu.style.display = "none", emptyContextMenu.style.display = "none";
}
function closeSearchModal() {
searchModal.style.display = "none", clearTimeout(searchTimeout);
}
function performModalSearch() {
let searchText = modalSearchInput.value.trim();
if (searchText.length === 0) {
modalSearchResults.innerHTML = "";
return;
}
let filteredResults = taskManager.getSearchResults(searchText).filter((result) => result.id !== activeNode.id());
if (modalSearchResults.innerHTML = "", selectedResultIndex = -1, filteredResults.length === 0) {
let createOption = document.createElement("div");
createOption.className = "search-result-item create-task-option", searchMode === "blocker" ? (createOption.innerHTML = \`<strong>Create blocker task:</strong> "\${searchText}"\`, createOption.addEventListener("click", function() {
createNewTaskAndBlocker(searchText);
})) : (createOption.innerHTML = \`<strong>Create task:</strong> "\${searchText}"\`, createOption.addEventListener("click", function() {
createNewTaskAndBlock(searchText);
})), createOption.dataset.createContent = searchText, modalSearchResults.appendChild(createOption);
return;
}
filteredResults.forEach((result, index) => {
let resultItem = document.createElement("div");
resultItem.className = "search-result-item", resultItem.textContent = result.label || result.content, resultItem.dataset.nodeId = result.id, resultItem.dataset.resultIndex = index, resultItem.addEventListener("click", function() {
searchMode === "blocker" ? createBlockerEdge(result.id) : createBlockingEdge(result.id);
}), modalSearchResults.appendChild(resultItem);
});
}
function createBlockingEdge(targetNodeId) {
if (!activeNode || !targetNodeId) return;
let sourceNodeId = activeNode.id();
if (taskManager.wouldCreateCycle(sourceNodeId, targetNodeId)) {
alert("Cannot create this dependency - it would create a cycle!");
return;
}
let sourceTask = taskManager.getTasks().find((t) => t.uuid === sourceNodeId);
if (sourceTask) {
let newLink = \`[\${targetNodeId}](https://www.amplenote.com/notes/tasks/\${targetNodeId}?relation=blocking)\`, newContent = sourceTask.content + " " + newLink;
taskManager.updateTask(sourceTask.uuid, { content: newContent.trim() }), console.log(\`Created blocking dependency: \${sourceNodeId} -> \${targetNodeId}\`);
}
closeSearchModal();
}
async function createBlockerEdge(sourceNodeId) {
if (!activeNode || !sourceNodeId) return;
let targetNodeId = activeNode.id();
if (!await taskManager.createBlockerDependency(sourceNodeId, targetNodeId)) {
alert("Cannot create this dependency - it would create a cycle!");
return;
}
console.log(\`Created blocker dependency: \${sourceNodeId} -> \${targetNodeId}\`), closeSearchModal();
}
async function createNewTaskAndBlock(taskContent) {
if (!(!activeNode || !taskContent.trim()))
try {
let noteUUID = window.noteUUID || "note-1", taskUUID = await taskAPIService.insertTask(noteUUID, { content: taskContent.trim() }), createdTask = {
uuid: taskUUID,
content: taskContent.trim(),
completedAt: null,
noteUUID,
score: 1
};
taskManager.addTask(createdTask);
let sourceNodeId = activeNode.id(), sourceTask = taskManager.getTasks().find((t) => t.uuid === sourceNodeId);
if (sourceTask) {
let newLink = \`[\${taskUUID}](https://www.amplenote.com/notes/tasks/\${taskUUID}?relation=blocking)\`, newContent = sourceTask.content + " " + newLink;
taskManager.updateTask(sourceTask.uuid, { content: newContent.trim() }), console.log(\`Created new task "\${taskContent}" and dependency: \${sourceNodeId} -> \${taskUUID}\`);
}
closeSearchModal();
} catch (error) {
console.error("Failed to create task and dependency:", error), alert("Failed to create task. Please try again.");
}
}
async function createNewTaskAndBlocker(taskContent) {
if (!(!activeNode || !taskContent.trim()))
try {
let targetNodeId = activeNode.id(), noteUUID = window.noteUUID || "note-1", taskUUID = await taskAPIService.insertTask(noteUUID, { content: taskContent.trim() }), createdTask = {
uuid: taskUUID,
content: taskContent.trim(),
completedAt: null,
noteUUID,
score: 1
};
taskManager.addTask(createdTask), await taskManager.createBlockerDependency(taskUUID, targetNodeId), console.log(\`Created new blocker task "\${taskContent}" and dependency: \${taskUUID} -> \${targetNodeId}\`), closeSearchModal();
} catch (error) {
console.error("Failed to create blocker task and dependency:", error), alert("Failed to create blocker task. Please try again.");
}
}
closeSearchModalBtn.addEventListener("click", closeSearchModal), searchModal.addEventListener("click", function(event) {
event.target === searchModal && closeSearchModal();
}), modalSearchInput.addEventListener("input", function() {
clearTimeout(searchTimeout), searchTimeout = setTimeout(performModalSearch, 300);
}), modalSearchInput.addEventListener("keydown", function(event) {
let resultItems = modalSearchResults.querySelectorAll(".search-result-item");
if (event.key === "ArrowDown")
event.preventDefault(), selectedResultIndex = Math.min(selectedResultIndex + 1, resultItems.length - 1), updateSelectedResult(resultItems);
else if (event.key === "ArrowUp")
event.preventDefault(), selectedResultIndex = Math.max(selectedResultIndex - 1, -1), updateSelectedResult(resultItems);
else if (event.key === "Enter") {
if (event.preventDefault(), selectedResultIndex >= 0 && resultItems[selectedResultIndex]) {
let selectedItem = resultItems[selectedResultIndex];
if (selectedItem.dataset.createContent)
searchMode === "blocker" ? createNewTaskAndBlocker(selectedItem.dataset.createContent) : createNewTaskAndBlock(selectedItem.dataset.createContent);
else {
let nodeId = selectedItem.dataset.nodeId;
searchMode === "blocker" ? createBlockerEdge(nodeId) : createBlockingEdge(nodeId);
}
}
} else event.key === "Escape" && closeSearchModal();
});
function updateSelectedResult(resultItems) {
resultItems.forEach((item, index) => {
index === selectedResultIndex ? item.classList.add("selected") : item.classList.remove("selected");
});
}
try {
let noteUUID = window.noteUUID || "note-1", tasks = await taskAPIService.getNoteTasks(noteUUID, { includeDone: !0 });
console.log(\`\\u{1F4CB} Loaded \${tasks.length} tasks from API service\`), taskManager.setTasks(tasks), environment === "development" && setTimeout(() => {
let nodes = cy.nodes();
console.log(\`\\u{1F3B2} Adding random important/urgent properties to \${nodes.length} tasks\`), nodes.forEach((node, index) => {
let important = Math.random() > 0.5, urgent = Math.random() > 0.5;
node.data("important", important), node.data("urgent", urgent), console.log(\`Task \${node.id()}: important=\${important}, urgent=\${urgent}\`);
}), console.log("\\u2705 Random important/urgent properties added to all tasks");
}, 200), taskManager.startAutoSync(noteUUID), console.log("Auto-sync enabled for real-time note updates");
} catch (error) {
console.error("Failed to load tasks:", error), taskManager.setTasks([]);
}
cy.on("ehcomplete", function(event, sourceNode, targetNode, addedEles) {
let sourceTask = taskManager.getTasks().find((t) => t.uuid === sourceNode.id());
if (sourceTask) {
let targetId = targetNode.id(), newLink = \`[\${targetId}](https://www.amplenote.com/notes/tasks/\${targetId}?relation=blocking)\`, newContent = sourceTask.content + " " + newLink;
taskManager.updateTask(sourceTask.uuid, { content: newContent.trim() });
}
});
let jsonViewer = document.getElementById("json-viewer");
function updateJsonViewer(taskManager2) {
let tasks = taskManager2.getTasks();
jsonViewer.value = JSON.stringify(tasks, null, 2), jsonViewer.style.border = "1px solid var(--color-border-default)";
}
let isUpdatingFromGraph = !1;
async function syncJsonToGraph() {
if (!isUpdatingFromGraph)
try {
let newTasks = JSON.parse(jsonViewer.value);
if (!Array.isArray(newTasks))
throw new Error("JSON must be an array of tasks");
newTasks.forEach((task) => {
if (!task.uuid || !task.content)
throw new Error("Each task must have uuid and content fields");
}), isUpdatingFromGraph = !0;
let currentTasks = taskManager.getTasks(), currentTaskMap = new Map(currentTasks.map((task) => [task.uuid, task])), newTaskMap = new Map(newTasks.map((task) => [task.uuid, task]));
for (let newTask of newTasks) {
let currentTask = currentTaskMap.get(newTask.uuid);
if (currentTask) {
if (currentTask.content !== newTask.content || currentTask.completedAt !== newTask.completedAt || currentTask.noteUUID !== newTask.noteUUID || currentTask.score !== newTask.score)
if (console.log(\`Updating task \${newTask.uuid} via JSON editor\`), currentTask.completedAt && !newTask.completedAt)
try {
await taskManager.reopenTask(newTask.uuid), console.log(\`Reopened task \${newTask.uuid} via JSON editor with cascading\`);
let otherUpdates = {};
currentTask.content !== newTask.content && (otherUpdates.content = newTask.content), currentTask.noteUUID !== newTask.noteUUID && (otherUpdates.noteUUID = newTask.noteUUID), currentTask.score !== newTask.score && (otherUpdates.score = newTask.score), Object.keys(otherUpdates).length > 0 && await taskManager.updateTask(newTask.uuid, otherUpdates);
} catch (error) {
console.error(\`Failed to reopen task \${newTask.uuid}:\`, error.message);
}
else {
let updates = {};
currentTask.content !== newTask.content && (updates.content = newTask.content), currentTask.completedAt !== newTask.completedAt && (updates.completedAt = newTask.completedAt), currentTask.noteUUID !== newTask.noteUUID && (updates.noteUUID = newTask.noteUUID), currentTask.score !== newTask.score && (updates.score = newTask.score);
try {
await taskManager.updateTask(newTask.uuid, updates);
} catch (error) {
console.error(\`Failed to update task \${newTask.uuid}:\`, error.message);
}
}
} else {
console.log(\`Adding new task \${newTask.uuid} via JSON editor\`);
try {
taskManager.addTask(newTask);
} catch (error) {
console.error(\`Failed to add task \${newTask.uuid}:\`, error.message);
}
}
}
let deletedTasks = currentTasks.filter((task) => !newTaskMap.has(task.uuid));
deletedTasks.length > 0 && console.log(\`Note: \${deletedTasks.length} task(s) removed from JSON but not deleted from graph (deletion not yet supported)\`), isUpdatingFromGraph = !1, jsonViewer.style.border = "1px solid var(--color-border-default)";
} catch (e) {
console.error("Invalid JSON or update failed:", e), jsonViewer.style.border = "1px solid #da3633", isUpdatingFromGraph = !1;
}
}
jsonViewer.addEventListener("input", function() {
jsonViewer.style.border = "1px solid #f79000";
}), jsonViewer.addEventListener("blur", syncJsonToGraph), jsonViewer.addEventListener("keydown", function(e) {
e.ctrlKey && e.key === "s" && (e.preventDefault(), syncJsonToGraph());
});
function updateJsonViewerSafe(taskManager2) {
if (!isUpdatingFromGraph) {
let tasks = taskManager2.getTasks();
jsonViewer.value = JSON.stringify(tasks, null, 2), jsonViewer.style.border = "1px solid var(--color-border-default)";
}
}
let toggleJsonBtn = document.getElementById("toggle-json"), jsonToggleText = document.getElementById("json-toggle-text"), toggleLeafBtn = document.getElementById("toggle-leaf-highlight"), leafToggleText = document.getElementById("leaf-toggle-text"), viewToggleBtn = document.getElementById("view-toggle"), viewIcon = document.getElementById("view-icon"), graphInstructions = document.getElementById("graph-instructions"), matrixInstructions = document.getElementById("matrix-instructions"), isJsonCollapsed = !0, isLeafHighlighted = !1, isEisenhowerMode = !1;
function initializeSidebarState() {
let hasCollapsedClass = document.body.classList.contains("sidebar-collapsed");
document.body.classList.add("sidebar-collapsed"), jsonToggleText.textContent = "Show debug JSON data", isJsonCollapsed = !0;
}
initializeSidebarState();
function updateInstructions() {
isEisenhowerMode ? (graphInstructions.style.display = "none", matrixInstructions.style.display = "block") : (graphInstructions.style.display = "block", matrixInstructions.style.display = "none");
}
updateInstructions();
let LEAF_HIGHLIGHT_COLOR = "rgb(255, 144, 144)", LEAF_TEXT_COLOR = "#000000";
toggleJsonBtn.addEventListener("click", function() {
isJsonCollapsed = !isJsonCollapsed, isJsonCollapsed ? (document.body.classList.add("sidebar-collapsed"), jsonToggleText.textContent = "Show debug JSON data") : (document.body.classList.remove("sidebar-collapsed"), jsonToggleText.textContent = "Hide debug JSON data"), emptyContextMenu.style.display = "none";
}), toggleLeafBtn.addEventListener("click", function() {
isLeafHighlighted = !isLeafHighlighted, isLeafHighlighted ? (highlightLeafNodes(), leafToggleText.textContent = "Don't highlight leaf nodes") : (clearLeafHighlight(), leafToggleText.textContent = "Highlight leaf nodes"), emptyContextMenu.style.display = "none";
}), viewToggleBtn.addEventListener("click", function() {
viewIcon.classList.add("rotate-out"), setTimeout(() => {
isEisenhowerMode = !isEisenhowerMode, isEisenhowerMode ? (console.log("Switching to Matrix mode"), document.getElementById("cy").style.display = "none", document.getElementById("eisenhower-matrix").style.display = "block", viewIcon.textContent = "grid_view", viewToggleBtn.title = "Switch to Graph view", loadTasksIntoQuadrants(), setupQuadrantDropZones()) : (console.log("Switching back to Graph mode"), document.getElementById("cy").style.display = "block", document.getElementById("eisenhower-matrix").style.display = "none", viewIcon.textContent = "account_tree", viewToggleBtn.title = "Switch to Matrix view"), updateInstructions(), viewIcon.classList.remove("rotate-out"), viewIcon.classList.add("rotate-in"), setTimeout(() => {
viewIcon.classList.remove("rotate-in");
}, 300);
}, 150);
});
function loadTasksIntoQuadrants() {
console.log("Loading tasks into quadrants"), document.querySelectorAll(".quadrant .task-list").forEach((list) => {
list.innerHTML = "";
});
let tasks = taskManager.getTasks();
console.log(\`Found \${tasks.length} tasks to categorize\`);
let activeTasks = tasks.filter((task) => !task.completedAt && !task.dismissedAt);
console.log(\`Filtered to \${activeTasks.length} active tasks for matrix view\`), activeTasks.forEach((task) => {
let taskElement = createTaskElement(task), quadrant = categorizeTask(task), quadrantElement = document.getElementById(quadrant);
quadrantElement && quadrantElement.querySelector(".task-list").appendChild(taskElement);
});
}
function categorizeTask(task) {
let node = cy.$id(task.uuid), important = node.length > 0 && node.data("important") || !1, urgent = node.length > 0 && node.data("urgent") || !1;
return important && urgent ? "quadrant-urgent-important" : important && !urgent ? "quadrant-not-urgent-important" : !important && urgent ? "quadrant-urgent-not-important" : "quadrant-not-urgent-not-important";
}
function createTaskElement(task) {
let taskDiv = document.createElement("div");
taskDiv.className = "task-item", taskDiv.setAttribute("data-task-id", task.uuid), taskDiv.draggable = !0;
let rawContent = task.content || "Untitled Task", strippedContent = taskManager.stripDependencyLinks(rawContent), processedContent = taskManager.processTaskContent(strippedContent, !0), isCompleted = task.completedAt || task.dismissedAt, node = cy.$id(task.uuid), important = node.length > 0 && node.data("important") || !1, urgent = node.length > 0 && node.data("urgent") || !1;
return taskDiv.innerHTML = \`
<div class="task-content \${isCompleted ? "completed" : ""}">\${processedContent}</div>
\`, taskDiv.addEventListener("dragstart", handleTaskDragStart), taskDiv.addEventListener("dragend", handleTaskDragEnd), taskDiv;
}
let draggedTask = null;
function handleTaskDragStart(e) {
draggedTask = e.target, e.target.style.opacity = "0.5", console.log("Started dragging task:", e.target.getAttribute("data-task-id")), console.log("Dragged task element:", draggedTask);
}
function handleTaskDragEnd(e) {
e.target.style.opacity = "1", draggedTask = null, console.log("Ended dragging task");
}
function setupQuadrantDropZones() {
let quadrants = document.querySelectorAll(".quadrant");
console.log("Setting up drop zones for", quadrants.length, "quadrants"), quadrants.forEach((quadrant) => {
console.log("Setting up drop zone for quadrant:", quadrant.id), quadrant.addEventListener("dragover", handleDragOver), quadrant.addEventListener("dragenter", handleDragEnter), quadrant.addEventListener("dragleave", handleDragLeave), quadrant.addEventListener("drop", handleDrop);
});
}
function handleDragOver(e) {
e.preventDefault();
}
function handleDragEnter(e) {
e.preventDefault(), e.currentTarget.classList.add("drag-over");
}
function handleDragLeave(e) {
e.currentTarget.contains(e.relatedTarget) || e.currentTarget.classList.remove("drag-over");
}
async function handleDrop(e) {
if (e.preventDefault(), e.currentTarget.classList.remove("drag-over"), !draggedTask) {
console.log("No dragged task found");
return;
}
let taskId = draggedTask.getAttribute("data-task-id"), quadrantId = e.currentTarget.id;
console.log(\`Dropped task \${taskId} on quadrant \${quadrantId}\`);
let newProperties = getPropertiesFromQuadrant(quadrantId);
console.log("New properties:", newProperties);
try {
await taskManager.updateTask(taskId, {
important: newProperties.important,
urgent: newProperties.urgent
}), console.log(\`Updated task \${taskId} with important: \${newProperties.important}, urgent: \${newProperties.urgent}\`);
} catch (error) {
console.error(\`Failed to update task \${taskId}:\`, error);
return;
}
console.log("Refreshing quadrants..."), loadTasksIntoQuadrants(), console.log(\`Successfully moved task \${taskId} to \${quadrantId}\`);
}
function getPropertiesFromQuadrant(quadrantId) {
switch (quadrantId) {
case "quadrant-urgent-important":
return { important: !0, urgent: !0 };
case "quadrant-not-urgent-important":
return { important: !0, urgent: !1 };
case "quadrant-urgent-not-important":
return { important: !1, urgent: !0 };
case "quadrant-not-urgent-not-important":
return { important: !1, urgent: !1 };
default:
return { important: !1, urgent: !1 };
}
}
function highlightLeafNodes() {
cy.nodes().forEach(function(node) {
cy.edges(\`[source = "\${node.id()}"]\`).length === 0 && node.style({
"background-color": LEAF_HIGHLIGHT_COLOR,
color: LEAF_TEXT_COLOR
});
}), console.log("Highlighted leaf nodes");
}
function clearLeafHighlight() {
cy.nodes().forEach(function(node) {
node.removeStyle("background-color color");
}), console.log("Cleared leaf node highlighting");
}
updateJsonViewer(taskManager);
let selectedNodeId = null;
cy.on("click", "node", function(evt) {
if (console.log("Node click detected:", evt.target.id(), "Ctrl key:", evt.originalEvent && evt.originalEvent.ctrlKey), evt.originalEvent && evt.originalEvent.ctrlKey) {
evt.stopPropagation(), evt.preventDefault();
let nodeId = evt.target.id();
console.log("Processing Ctrl+click for node:", nodeId), selectedNodeId === nodeId ? clearNodeSelection() : selectNode(nodeId);
}
});
let clickTimer = null;
cy.on("mousedown", "node", function(evt) {
clickTimer && (clearTimeout(clickTimer), clickTimer = null), clickTimer = setTimeout(() => {
let nodeId = evt.target.id();
selectedNodeId === nodeId ? clearNodeSelection() : selectNode(nodeId), clickTimer = null;
}, 200);
}), cy.on("ehstart", function() {
clickTimer && (clearTimeout(clickTimer), clickTimer = null);
}), cy.on("click", function(evt) {
evt.target === cy && clearNodeSelection();
}), document.addEventListener("keydown", function(evt) {
evt.key === "Escape" && clearNodeSelection();
});
function selectNode(nodeId) {
console.log("selectNode called with:", nodeId), clearNodeSelection(), selectedNodeId = nodeId;
let ancestors = findNodeAncestors(nodeId);
console.log("Found ancestors:", Array.from(ancestors)), cy.nodes().forEach(function(node) {
let currentNodeId = node.id();
currentNodeId === nodeId ? (console.log("Applying orange border to:", currentNodeId), node.style({
"border-color": "#ff8c00",
"border-width": "3px",
"border-style": "solid"
})) : ancestors.has(currentNodeId) ? (console.log("Applying green border to:", currentNodeId), node.style({
"border-color": "#4CAF50",
"border-width": "3px",
"border-style": "solid"
})) : (console.log("Applying dimmed style to:", currentNodeId), node.style({
opacity: "0.1"
}));
}), console.log(\`Selected node \${nodeId} with \${ancestors.size} ancestors\`);
}
function clearNodeSelection() {
selectedNodeId !== null && (cy.nodes().forEach(function(node) {
node.removeStyle("border-color border-width border-style opacity"), isLeafHighlighted && cy.edges(\`[source = "\${node.id()}"]\`).length === 0 && node.style({
"background-color": LEAF_HIGHLIGHT_COLOR,
color: LEAF_TEXT_COLOR
});
}), selectedNodeId = null, console.log("Cleared node selection"));
}
function findNodeAncestors(nodeId) {
let ancestors = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new Set();
function traverseAncestors(currentNodeId) {
if (visited.has(currentNodeId)) return;
visited.add(currentNodeId), cy.edges(\`[target = "\${currentNodeId}"]\`).forEach(function(edge) {
let sourceId = edge.source().id();
ancestors.add(sourceId), traverseAncestors(sourceId);
});
}
return traverseAncestors(nodeId), ancestors;
}
cy.on("cxttap", "node", function(evt) {
clearNodeSelection();
}), cy.on("cxttap", "edge", function(evt) {
clearNodeSelection();
}), cy.on("cxttap", function(evt) {
evt.target === cy && clearNodeSelection();
});
function openSearchModalWithClear(mode = "block") {
clearNodeSelection(), openSearchModal(mode);
}
searchToBlockBtn.addEventListener("click", function() {
activeNode && openSearchModalWithClear("block");
}), searchForBlockerBtn.addEventListener("click", function() {
activeNode && openSearchModalWithClear("blocker");
}), redrawGraphBtn.addEventListener("click", function() {
emptyContextMenu.style.display = "none", clearNodeSelection(), cy.layout({
...defaultConfig.layout,
fit: !0,
padding: 50
}).run(), console.log("Graph layout redrawn");
}), cy.on("add remove data", "node", () => {
updateJsonViewerSafe(taskManager), clearNodeSelection(), isLeafHighlighted && setTimeout(() => {
clearLeafHighlight(), highlightLeafNodes();
}, 100), isEisenhowerMode && setTimeout(() => {
loadTasksIntoQuadrants();
}, 100);
}), cy.on("add remove", "edge", () => {
updateJsonViewerSafe(taskManager), clearNodeSelection(), isLeafHighlighted && setTimeout(() => {
clearLeafHighlight(), highlightLeafNodes();
}, 100), isEisenhowerMode && setTimeout(() => {
loadTasksIntoQuadrants();
}, 100);
});
function initializeQuadrantTooltips() {
let tooltip = document.getElementById("quadrant-tooltip"), quadrants = document.querySelectorAll(".quadrant h3"), tooltipTexts = {
"quadrant-urgent-important": "Tasks that are both important and urgent. These should be done immediately as they have high impact and time pressure.",
"quadrant-not-urgent-important": "Tasks that are important but not urgent. Schedule these for later - they have high impact but no immediate deadline.",
"quadrant-urgent-not-important": "Tasks that are urgent but not important. Delegate these if possible - they need to be done soon but don't require your specific expertise.",
"quadrant-not-urgent-not-important": "Tasks that are neither urgent nor important. Consider eliminating these tasks as they provide little value and waste time."
};
quadrants.forEach((header) => {
let quadrantId = header.closest(".quadrant").id;
header.addEventListener("mouseenter", (e) => {
tooltip.textContent = tooltipTexts[quadrantId], tooltip.classList.add("show");
let rect = header.getBoundingClientRect(), isTopQuadrant = quadrantId === "quadrant-urgent-important" || quadrantId === "quadrant-not-urgent-important";
tooltip.style.left = rect.left + rect.width / 2 - tooltip.offsetWidth / 2 + "px", isTopQuadrant ? (tooltip.style.top = rect.bottom + 10 + "px", tooltip.classList.add("below"), tooltip.classList.remove("above")) : (tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + "px", tooltip.classList.add("above"), tooltip.classList.remove("below"));
}), header.addEventListener("mouseleave", () => {
tooltip.classList.remove("show", "above", "below");
});
});
}
initializeQuadrantTooltips();
}
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", initializeApp) : initializeApp();
})();
<\/script>
</head>
<body class="sidebar-collapsed">
<div id="instructions-container">
<button id="instructions-toggle" title="Toggle Instructions">
<span class="material-icons">help</span>
</button>
<div class="info hidden" id="instructions-panel">
<!-- Graph Mode Instructions -->
<div id="graph-instructions">
<b>Skill Tree Builder - Graph Mode</b><br><br>
<b>Controls:</b><br>
- Double-click empty space to create a new node<br>
- Double-click a node to edit its contents<br>
- Drag from a node to create dependency<br>
- Ctrl-Click on a node to highlight its prerequisite chain (Esc or click away to clear)<br>
- Right-click on a node to complete/reopen it<br>
- Right-click on a node to block a node on the graph<br>
- Right-click on empty space for options menu<br>
- Right-click on an edge to remove it<br><br>
<b>View Options (Right-click empty space):</b><br>
- Redraw graph layout<br>
- Toggle debug JSON data<br>
- Toggle leaf node highlighting<br><br>
<b>Behaviour:</b><br>
- Graph only shows tasks living in the current note<br>
- Reopening a task from this graph will reopen all of its descendants; reopening from Amplenote will not<br>
- Node selection shows: selected node (orange), prerequisites (green), others (dimmed)<br>
- Auto-sync with note updates every few seconds
</div>
<!-- Matrix Mode Instructions -->
<div id="matrix-instructions">
<b>Eisenhower Matrix - Prioritization Mode</b><br><br>
<b>Quadrants:</b><br>
- <b>DO</b> (\u2B50 top-left): Important & Urgent tasks<br>
- <b>SCHEDULE</b> (\u2606 top-right): Important but Not Urgent<br>
- <b>DELEGATE</b> (\u2B50 bottom-left): Not Important but Urgent<br>
- <b>DELETE</b> (\u{1F5D1}\uFE0F bottom-right): Neither Important nor Urgent<br><br>
<b>Controls:</b><br>
- Drag and drop tasks between quadrants to change priority<br>
- Changes are automatically saved and synced<br>
- View updates automatically when tasks change externally<br><br>
<b>Behaviour:</b><br>
- Auto-sync with note updates every few seconds
</div>
</div>
</div>
<div id="view-toggle-container">
<button id="view-toggle" title="Toggle between Graph and Matrix view">
<span class="material-icons view-toggle-icon" id="view-icon">account_tree</span>
</button>
</div>
<div id="textInputModal" class="modal">
<div class="modal-content">
<label id="modalPromptLabel" for="modalInput">Enter skill name:</label>
<textarea id="modalInput" name="modalInput" rows="5" spellcheck="false"></textarea>
<button id="modalSubmit">OK</button>
<button id="modalCancel" class="cancel">Cancel</button>
</div>
</div>
<div id="cy"></div>
<!-- Eisenhower Matrix HTML View -->
<div id="eisenhower-matrix" style="display: none;">
<div class="eisenhower-grid">
<div class="quadrant" id="quadrant-urgent-important">
<h3>
<span class="material-icons">star</span>
DO
</h3>
<div class="task-list"></div>
</div>
<div class="quadrant" id="quadrant-not-urgent-important">
<h3>
<span class="material-icons">star_border</span>
SCHEDULE
</h3>
<div class="task-list"></div>
</div>
<div class="quadrant" id="quadrant-urgent-not-important">
<h3>
<span class="material-icons">star_half</span>
DELEGATE
</h3>
<div class="task-list"></div>
</div>
<div class="quadrant" id="quadrant-not-urgent-not-important">
<h3>
<span class="material-icons">delete</span>
DELETE
</h3>
<div class="task-list"></div>
</div>
</div>
</div>
<!-- Node tooltip -->
<div id="node-tooltip"></div>
<!-- Quadrant tooltip -->
<div id="quadrant-tooltip" class="tooltip"></div>
<div id="editor-container">
<textarea id="json-viewer" spellcheck="false"></textarea>
</div>
<div class="context-menu" id="node-context-menu">
<div class="context-menu-item" id="complete-task">Complete Task</div>
<div class="context-menu-item" id="reopen-task">Reopen Task</div>
<div class="context-menu-item" id="search-to-block">\u{1F517} Search task to block</div>
<div class="context-menu-item" id="search-for-blocker">\u2B05\uFE0F Search for blocker task</div>
</div>
<div class="context-menu" id="edge-context-menu">
<div class="context-menu-item" id="remove-dependency">Remove Dependency</div>
</div>
<div class="context-menu" id="empty-context-menu">
<div class="context-menu-item" id="redraw-graph">
<span class="material-icons" style="margin-right: 8px; font-size: 16px;">refresh</span>
<span>Redraw Graph Layout</span>
</div>
<div class="context-menu-item" id="toggle-json">
<span class="material-icons" style="margin-right: 8px; font-size: 16px;">code</span>
<span id="json-toggle-text">Show debug JSON data</span>
</div>
<div class="context-menu-item" id="toggle-leaf-highlight">
<span class="material-icons" style="margin-right: 8px; font-size: 16px;">highlight</span>
<span id="leaf-toggle-text">Highlight leaf nodes</span>
</div>
</div>
<!-- Search Modal -->
<div id="search-modal" class="modal">
<div class="modal-content">
<h3 id="search-modal-title">Search for task to block</h3>
<input type="text" id="modal-search-input" placeholder="Type to search tasks..." autocomplete="off">
<div id="modal-search-results" class="modal-search-results"></div>
<button id="close-search-modal" class="cancel">Cancel</button>
</div>
</div>
</body>
</html>
`;
var addScriptToHtmlString = (htmlString, scriptContent) => {
let doc = new DOMParser().parseFromString(htmlString, "text/html"), script = doc.createElement("script");
script.textContent = scriptContent;
let head = doc.head;
return head.firstChild ? head.insertBefore(script, head.firstChild) : head.appendChild(script), doc.documentElement.outerHTML.replaceAll("\\x3Cscript>", () => "<script>");
}, addWindowVariableToHtmlString = (htmlString, variableName, variableValue) => {
let scriptContent = `window.${variableName} = ${JSON.stringify(variableValue)};`;
return addScriptToHtmlString(htmlString, scriptContent);
};
var COMMON_EMBED_COMMANDS = {
navigate: async (app, url) => {
if (await app.navigate(url)) return !0;
try {
return window.open(url, "_blank") != null;
} catch {
return !1;
}
},
prompt: async (app, ...args) => await app.prompt(...args),
alert: async (app, ...args) => await app.alert(...args),
getSettings: async (app) => app.settings,
setSetting: async (app, key, value) => app.setSetting(key, value),
getNoteTitleByUUID: async (app, noteUUID) => {
var _a;
return (_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.name;
},
getNoteContentByUUID: async (app, noteUUID) => await app.notes.find(noteUUID) ? await app.getNoteContent({ uuid: noteUUID }) : null,
getNoteBacklinksByUUID: async (app, noteUUID) => {
var _a;
return await ((_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.backlinks());
},
getNoteTagsByUUID: async (app, noteUUID) => {
var _a;
return (_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.tags;
},
getNoteSections: async (app, ...args) => await app.getNoteSections(...args),
getNoteTasks: async (app, noteHandle, options = {}) => {
let finalOptions = { includeDone: !0, ...options };
return await app.getNoteTasks(noteHandle, finalOptions);
},
insertTask: async (app, ...args) => await app.insertTask(...args),
updateTask: async (app, ...args) => await app.updateTask(...args),
createNote: async (app, ...args) => await app.createNote(...args),
deleteNote: async (app, ...args) => await app.deleteNote(...args),
replaceNoteContent: async (app, ...args) => await app.replaceNoteContent(...args),
setNoteName: async (app, ...args) => await app.setNoteName(...args),
addNoteTag: async (app, ...args) => await app.addNoteTag(...args),
removeNoteTag: async (app, ...args) => await app.removeNoteTag(...args),
insertNoteContent: async (app, ...args) => await app.insertNoteContent(...args),
getTaskDomains: async (app) => await app.getTaskDomains(),
getTaskDomainTasks: async (app, ...args) => await app.getTaskDomainTasks(...args),
getTask: async (app, ...args) => await app.getTask(...args),
findNote: async (app, ...args) => await app.findNote(...args),
filterNotes: async (app, ...args) => await app.filterNotes(...args),
saveFile: async (app, ...args) => {
try {
let { name, data } = args[0];
return data.startsWith("data:") && (data = await (await fetch(data)).blob()), await app.saveFile(data, name);
} catch (e) {
throw e.message;
}
}
};
function createOnEmbedCallHandler(embedCommands = {}, logBlacklistedCommands = []) {
return async function(app, commandName, ...args) {
try {
if (commandName in embedCommands) {
let result = await embedCommands[commandName](app, ...args);
return logBlacklistedCommands.includes(commandName) || console.debug("onEmbedCall:", commandName, args, result), result;
}
} catch (e) {
throw app.alert("Error:", e.message || e), console.error("onEmbedCall:", commandName, args, e), e;
}
throw new Error(`Unknown command: ${commandName}`);
};
}
var plugin = {
noteOption: {
"Create skilltree from note (sidebar)": async function(app) {
let noteUUID = app.context.noteUUID;
noteUUID || app.alert("No note selected"), await app.openSidebarEmbed(1, "sidebar", noteUUID);
},
"Create skilltree from note (fullscreen)": async function(app) {
let noteUUID = app.context.noteUUID;
noteUUID || app.alert("No note selected"), await app.openEmbed("fullscreen", noteUUID), await app.navigate("https://www.amplenote.com/notes/plugins/" + app.context.pluginUUID);
}
},
onEmbedCall: createOnEmbedCallHandler(COMMON_EMBED_COMMANDS),
renderEmbed(app, embedType, noteUUID) {
return addWindowVariableToHtmlString(embed_default, "noteUUID", noteUUID);
}
}, plugin_default = plugin;
return plugin;
})()