diff --git a/src/components/MiniGraph.astro b/src/components/MiniGraph.astro
index bf1b5ca..618d0a4 100644
--- a/src/components/MiniGraph.astro
+++ b/src/components/MiniGraph.astro
@@ -30,6 +30,9 @@ const relatedPostsTags = relatedPosts
.flatMap(post => post.data.tags || [])
.filter(tag => !tags.includes(tag)); // Exclude current post tags to avoid duplicates
+// Create a set of all Level 1 nodes' tags for filtering Level 2 tags
+const level1TagsSet = new Set([...tags, ...relatedPostsTags]);
+
// Get Level 2 posts: posts related to Level 1 tags (excluding current post and Level 1 posts)
const level2PostIds = new Set();
const level2Posts = [];
@@ -38,21 +41,22 @@ const level2Posts = [];
tags.forEach(tag => {
allPosts.forEach(post => {
// Skip if post is current post or already in related posts
- if (post.slug === slug || relatedPosts.some(rp => rp.slug === post.slug)) {
+ if (post.slug === slug || relatedPosts.some(rp => rp.slug === post.slug) || level2PostIds.has(post.slug)) {
return;
}
- // If post has the tag and isn't already added
- if (post.data.tags?.includes(tag) && !level2PostIds.has(post.slug)) {
+ // If post has the tag, add it to Level 2
+ if (post.data.tags?.includes(tag)) {
level2PostIds.add(post.slug);
level2Posts.push(post);
}
});
});
-// Get Level 2 tags from Level 2 posts
+// Only collect Level 2 tags that are directly linked to Level 1 posts
+// This fixes the issue where unrelated Level 2 tags were being included
const level2Tags = new Set();
-level2Posts.forEach(post => {
+relatedPosts.forEach(post => {
(post.data.tags || []).forEach(tag => {
// Only add if not already in Level 0 or Level 1 tags
if (!tags.includes(tag) && !relatedPostsTags.includes(tag)) {
@@ -70,14 +74,16 @@ const nodes = [
type: "post",
level: 0,
category: category,
- tags: tags
+ tags: tags,
+ url: `/posts/${slug}/`
},
// Level 1: Tag nodes
...tags.map(tag => ({
id: `tag-${tag}`,
label: tag,
type: "tag",
- level: 1
+ level: 1,
+ url: `/tag/${tag}/`
})),
// Level 1: Related post nodes
...relatedPosts.map(post => ({
@@ -86,30 +92,35 @@ const nodes = [
type: "post",
level: 1,
category: post.data.category || "Uncategorized",
- tags: post.data.tags || []
+ tags: post.data.tags || [],
+ url: `/posts/${post.slug}/`
})),
- // Level 2: Related tags nodes
+ // Level 2: Related tags nodes (Tags from Level 1 posts)
...relatedPostsTags.map(tag => ({
id: `tag-${tag}`,
label: tag,
type: "tag",
- level: 2
+ level: 2,
+ url: `/tag/${tag}/`
})),
- // Level 2: Posts related to tags
+ // Level 2: Posts related to tags (Posts connected to Level 1 tags)
...level2Posts.map(post => ({
id: post.slug,
label: post.data.title,
type: "post",
level: 2,
category: post.data.category || "Uncategorized",
- tags: post.data.tags || []
+ tags: post.data.tags || [],
+ url: `/posts/${post.slug}/`
})),
- // Level 2: Tags from Level 2 posts
+ // Level 2: Tags from Level 1 posts (only tags directly connected to Level 1 posts)
+ // This was the corrected logic for level2Tags Set
...[...level2Tags].map(tag => ({
id: `tag-${tag}`,
label: tag.toString(),
type: "tag",
- level: 2
+ level: 2,
+ url: `/tag/${tag.toString()}/`
}))
];
@@ -132,7 +143,7 @@ const edges = [
(post.data.tags || []).map(tag => ({
source: post.slug,
target: `tag-${tag}`,
- type: "post-tag"
+ type: "post-tag" // Re-using post-tag type for simplicity
}))
),
// Level 1 to Level 2: Tags to related posts
@@ -140,11 +151,12 @@ const edges = [
tags.filter(tag => post.data.tags?.includes(tag)).map(tag => ({
source: `tag-${tag}`,
target: post.slug,
- type: "tag-post"
+ type: "tag-post" // New type for tag -> post connection
}))
)
];
+
// Prepare graph data object
const graphData = { nodes, edges };
@@ -162,7 +174,7 @@ const predefinedColors = {
Post Connections
-
+
-
+
@@ -258,7 +270,7 @@ const predefinedColors = {
border: 1px solid var(--card-border, rgba(56, 189, 248, 0.2));
background: rgba(15, 23, 42, 0.2);
}
-
+
/* Fullscreen toggle button */
.fullscreen-toggle {
position: absolute;
@@ -277,22 +289,28 @@ const predefinedColors = {
z-index: 2;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ opacity: 0; /* Hidden by default, shown on hover */
}
-
+
+ .knowledge-graph-wrapper:hover .fullscreen-toggle {
+ opacity: 0.7; /* Show on hover */
+ }
+
.fullscreen-toggle:hover {
+ opacity: 1;
background: rgba(30, 41, 59, 0.9);
transform: translateY(-2px);
}
-
+
.fullscreen-icon {
width: 16px;
height: 16px;
}
-
+
.hidden {
display: none;
}
-
+
/* Fullscreen container */
.fullscreen-container {
display: none; /* Hidden by default */
@@ -306,11 +324,11 @@ const predefinedColors = {
flex-direction: column;
overflow: hidden;
}
-
+
.fullscreen-container.active {
display: flex;
}
-
+
.fullscreen-header {
display: flex;
justify-content: space-between;
@@ -320,13 +338,13 @@ const predefinedColors = {
background: rgba(15, 23, 42, 0.8);
backdrop-filter: blur(8px);
}
-
+
.fullscreen-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary, #e2e8f0);
}
-
+
.exit-fullscreen {
width: 36px;
height: 36px;
@@ -340,26 +358,26 @@ const predefinedColors = {
cursor: pointer;
transition: all 0.2s ease;
}
-
+
.exit-fullscreen:hover {
background: rgba(30, 41, 59, 0.9);
}
-
+
.fullscreen-content {
flex: 1;
display: flex;
overflow: hidden;
}
-
+
.fullscreen-graph {
flex: 1;
height: 100%;
border-right: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
}
-
+
/* Info panel */
.info-panel {
- width: 0;
+ width: 0; /* Hidden by default */
height: 100%;
background: var(--bg-secondary, #1e293b);
transition: width 0.3s ease;
@@ -367,11 +385,11 @@ const predefinedColors = {
display: flex;
flex-direction: column;
}
-
+
.info-panel.active {
- width: 350px;
+ width: 350px; /* Show panel when active */
}
-
+
.info-panel-header {
display: flex;
justify-content: space-between;
@@ -379,14 +397,14 @@ const predefinedColors = {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-primary, rgba(56, 189, 248, 0.1));
}
-
+
.info-title {
font-size: 1.2rem;
margin: 0;
color: var(--text-primary, #e2e8f0);
font-weight: 600;
}
-
+
.close-info {
background: none;
border: none;
@@ -399,29 +417,29 @@ const predefinedColors = {
justify-content: center;
transition: all 0.2s ease;
}
-
+
.close-info:hover {
color: var(--text-primary, #e2e8f0);
background: rgba(255, 255, 255, 0.1);
}
-
+
.info-content {
padding: 1.5rem;
overflow-y: auto;
flex: 1;
}
-
+
.info-type, .info-category, .info-tags, .info-connections {
margin-bottom: 1.25rem;
}
-
+
.info-label {
display: block;
color: var(--text-secondary, #94a3b8);
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
-
+
.type-value {
display: inline-block;
padding: 0.25rem 0.75rem;
@@ -429,30 +447,30 @@ const predefinedColors = {
font-size: 0.85rem;
font-weight: 500;
}
-
+
.post-type {
background-color: rgba(59, 130, 246, 0.15);
color: #3B82F6;
}
-
+
.tag-type {
background-color: rgba(16, 185, 129, 0.15);
color: #10B981;
}
-
+
.category-value {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.85rem;
}
-
+
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
-
+
.tag {
background: rgba(16, 185, 129, 0.1);
color: #10B981;
@@ -462,12 +480,12 @@ const predefinedColors = {
cursor: pointer;
transition: all 0.2s ease;
}
-
+
.tag:hover {
background: rgba(16, 185, 129, 0.2);
transform: translateY(-2px);
}
-
+
.connections-list {
padding-left: 0;
list-style: none;
@@ -475,24 +493,25 @@ const predefinedColors = {
max-height: 150px;
overflow-y: auto;
}
-
+
.connections-list li {
color: var(--text-secondary, #94a3b8);
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
-
+
.connections-list a {
color: var(--accent-primary, #38bdf8);
text-decoration: none;
transition: color 0.2s ease;
+ cursor: pointer;
}
-
+
.connections-list a:hover {
color: var(--accent-secondary, #06b6d4);
text-decoration: underline;
}
-
+
.info-link {
display: block;
background: linear-gradient(90deg, var(--accent-primary, #38bdf8), var(--accent-secondary, #06b6d4));
@@ -506,32 +525,32 @@ const predefinedColors = {
box-shadow: 0 4px 10px rgba(6, 182, 212, 0.2);
margin-top: 1rem;
}
-
+
.info-link:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(6, 182, 212, 0.3);
}
-
+
/* Media queries for mobile responsiveness */
@media screen and (max-width: 768px) {
.fullscreen-content {
flex-direction: column;
}
-
+
.fullscreen-graph {
height: 60%;
border-right: none;
border-bottom: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
}
-
+
.info-panel {
width: 100%;
- height: 0;
+ height: 0; /* Start hidden */
}
-
+
.info-panel.active {
width: 100%;
- height: 40%;
+ height: 40%; /* Take remaining space */
}
}
@@ -578,13 +597,14 @@ const predefinedColors = {
const fullscreenExitIcon = document.getElementById(`${graphId}-fullscreen-exit`);
const infoPanel = document.getElementById(`${graphId}-info-panel`);
const closeInfoBtn = document.getElementById(`${graphId}-close-info`);
-
+
// State variables
let isFullscreen = false;
let cy = null; // Mini graph instance
let cyFullscreen = null; // Fullscreen graph instance
let originalStyles = {}; // To restore page layout after exiting fullscreen
-
+ let selectedNode = null; // Currently selected node for fullscreen interactions
+
try {
// Check if we have any nodes
if (graphData.nodes.length === 0) {
@@ -595,22 +615,22 @@ const predefinedColors = {
// Generate node colors based on type and category
const nodeElements = graphData.nodes.map(node => {
let nodeColor = '#3B82F6'; // Default blue for posts
-
+
if (node.type === 'tag') {
nodeColor = '#10B981'; // Green for tags
} else if (node.type === 'post' && node.category) {
// Use category color for posts if available
nodeColor = predefinedColors[node.category] || '#3B82F6';
}
-
+
// Adjust opacity based on level
- const levelOpacity = node.level === 0 ? 1 :
+ const levelOpacity = node.level === 0 ? 1 :
node.level === 1 ? 0.85 : 0.7;
-
+
// Calculate node size based on level
- const nodeSize = node.level === 0 ? 30 :
+ const nodeSize = node.level === 0 ? 30 :
node.level === 1 ? 22 : 16;
-
+
return {
data: {
id: node.id,
@@ -621,11 +641,12 @@ const predefinedColors = {
tags: node.tags || [],
color: nodeColor,
opacity: levelOpacity,
- size: nodeSize
+ size: nodeSize,
+ url: node.url || '#'
}
};
});
-
+
// Generate edge elements
const edgeElements = graphData.edges.map((edge, index) => ({
data: {
@@ -635,10 +656,10 @@ const predefinedColors = {
type: edge.type
}
}));
-
+
// Combine nodes and edges
const elements = [...nodeElements, ...edgeElements];
-
+
// Define common style array to be used by both graph instances
const graphStyles = [
// Base node style
@@ -754,7 +775,7 @@ const predefinedColors = {
}
}
];
-
+
// Initialize mini graph with cose layout
cy = cytoscape({
container,
@@ -783,115 +804,138 @@ const predefinedColors = {
maxZoom: 2,
wheelSensitivity: 0.2
});
-
+
// Fit graph to container
cy.fit(undefined, 20);
-
+
// Add event listeners
cy.on('tap', 'node', function(evt) {
const node = evt.target;
highlightNode(node, cy);
+ // Navigate on click in mini-graph
+ const url = node.data('url');
+ if (url && url !== '#') {
+ window.location.href = url;
+ }
});
-
+
// Add fullscreen toggle functionality if the button exists
if (fullscreenToggle && fullscreenContainer) {
fullscreenToggle.addEventListener('click', function() {
enterFullscreenMode();
});
}
-
+
// Exit fullscreen when button is clicked
if (exitFullscreenBtn) {
exitFullscreenBtn.addEventListener('click', function() {
exitFullscreenMode();
});
}
-
+
// Close info panel when close button is clicked
if (closeInfoBtn && infoPanel) {
closeInfoBtn.addEventListener('click', function() {
infoPanel.classList.remove('active');
});
}
-
+
// Toggle fullscreen mode by keyboard ESC
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isFullscreen) {
exitFullscreenMode();
}
});
-
+
// Function to enter fullscreen mode
function enterFullscreenMode() {
if (isFullscreen) return;
-
+
isFullscreen = true;
-
+
// Show fullscreen container
fullscreenContainer.classList.add('active');
-
+
// Update fullscreen toggle icons
fullscreenEnterIcon.classList.add('hidden');
fullscreenExitIcon.classList.remove('hidden');
-
+
// Store original styles of important page elements to restore later
saveOriginalStyles();
-
+
// Initialize fullscreen graph if not already done
if (!cyFullscreen && fullscreenGraph) {
initFullscreenGraph();
+ } else if (cyFullscreen) {
+ // If already initialized, just resize and fit
+ setTimeout(() => {
+ cyFullscreen.resize();
+ cyFullscreen.fit(undefined, 30);
+ cyFullscreen.center();
+ }, 50); // Small delay for transition
}
-
+
// Prevent body scroll
document.body.style.overflow = 'hidden';
}
-
+
// Function to exit fullscreen mode
function exitFullscreenMode() {
if (!isFullscreen) return;
-
+
isFullscreen = false;
-
+
// Hide fullscreen container
fullscreenContainer.classList.remove('active');
-
+
// Update fullscreen toggle icons
fullscreenEnterIcon.classList.remove('hidden');
fullscreenExitIcon.classList.add('hidden');
-
+
// Close info panel
if (infoPanel) {
infoPanel.classList.remove('active');
}
-
+
// Restore original page layout
restoreOriginalStyles();
-
+
// Allow body scroll again
document.body.style.overflow = '';
+
+ // Resize mini graph after exit
+ setTimeout(() => {
+ if (cy) {
+ cy.resize();
+ cy.fit(undefined, 20);
+ }
+ }, 50);
}
-
+
// Save original styles of page elements before entering fullscreen
function saveOriginalStyles() {
originalStyles = {
bodyOverflow: document.body.style.overflow,
// Add other elements that need to be restored later
};
-
+
// Check for sidebar, content containers, etc. and save their styles
- const sidebar = document.querySelector('.sidebar, aside, nav');
- const mainContent = document.querySelector('main, .content, article');
-
+ const sidebar = document.querySelector('.post-sidebar'); // Use specific class
+ const mainContent = document.querySelector('.post-main-column'); // Use specific class
+
if (sidebar) {
originalStyles.sidebar = {
element: sidebar,
display: sidebar.style.display,
visibility: sidebar.style.visibility,
position: sidebar.style.position,
- zIndex: sidebar.style.zIndex
+ zIndex: sidebar.style.zIndex,
+ width: sidebar.style.width, // Save width too
+ flex: sidebar.style.flex,
+ gridColumn: sidebar.style.gridColumn
};
}
-
+
if (mainContent) {
originalStyles.mainContent = {
element: mainContent,
@@ -901,33 +945,40 @@ const predefinedColors = {
};
}
}
-
+
// Restore original styles when exiting fullscreen
function restoreOriginalStyles() {
document.body.style.overflow = originalStyles.bodyOverflow || '';
-
+
// Restore sidebar if it exists
if (originalStyles.sidebar && originalStyles.sidebar.element) {
const sidebar = originalStyles.sidebar.element;
- sidebar.style.display = originalStyles.sidebar.display;
- sidebar.style.visibility = originalStyles.sidebar.visibility;
- sidebar.style.position = originalStyles.sidebar.position;
- sidebar.style.zIndex = originalStyles.sidebar.zIndex;
+ Object.assign(sidebar.style, originalStyles.sidebar); // Restore all saved styles
+ sidebar.style.removeProperty('position'); // Ensure position is reset if it was static
}
-
+
// Restore main content if it exists
if (originalStyles.mainContent && originalStyles.mainContent.element) {
const mainContent = originalStyles.mainContent.element;
- mainContent.style.marginLeft = originalStyles.mainContent.marginLeft;
- mainContent.style.width = originalStyles.mainContent.width;
- mainContent.style.maxWidth = originalStyles.mainContent.maxWidth;
+ Object.assign(mainContent.style, originalStyles.mainContent);
}
+
+ // Force reflow to ensure styles apply correctly
+ setTimeout(() => {
+ const contentArea = document.querySelector('.post-content');
+ if (contentArea) {
+ const display = contentArea.style.display;
+ contentArea.style.display = 'none';
+ void contentArea.offsetHeight; // Trigger reflow
+ contentArea.style.display = display;
+ }
+ }, 0);
}
-
+
// Initialize the fullscreen graph
function initFullscreenGraph() {
if (!fullscreenGraph) return;
-
+
// Create a new cytoscape instance for fullscreen with the same data
cyFullscreen = cytoscape({
container: fullscreenGraph,
@@ -956,34 +1007,36 @@ const predefinedColors = {
maxZoom: 3,
wheelSensitivity: 0.3
});
-
+
// Fit graph to container
cyFullscreen.fit(undefined, 30);
-
+
// Add fullscreen interactions
addFullscreenInteractions();
}
-
+
// Add interactions for fullscreen mode
function addFullscreenInteractions() {
// Click on node to show info
cyFullscreen.on('tap', 'node', function(evt) {
const node = evt.target;
+ selectedNode = node; // Store the selected node
const nodeData = node.data();
-
+
// Highlight the selected node
highlightNode(node, cyFullscreen);
-
+
// Show info panel
showNodeInfo(nodeData);
});
-
+
// Click on background to reset
cyFullscreen.on('tap', function(evt) {
if (evt.target === cyFullscreen) {
// Remove highlights
cyFullscreen.elements().removeClass('highlighted faded');
-
+ selectedNode = null;
+
// Hide info panel
if (infoPanel) {
infoPanel.classList.remove('active');
@@ -991,129 +1044,127 @@ const predefinedColors = {
}
});
}
-
+
// Highlight a node and its connections
function highlightNode(node, cyInstance) {
// Remove previous highlights
cyInstance.elements().removeClass('highlighted faded');
-
+
// Highlight selected node and its neighborhood
node.addClass('highlighted');
node.neighborhood().addClass('highlighted');
-
+
// Fade the rest
cyInstance.elements().difference(node.neighborhood().union(node)).addClass('faded');
}
-
+
// Show node info in the panel
function showNodeInfo(nodeData) {
if (!infoPanel) return;
-
- // Set node title
- document.getElementById(`${graphId}-info-title`).textContent = nodeData.label;
-
- // Set node type
- const typeEl = document.getElementById(`${graphId}-info-type`);
- typeEl.textContent = nodeData.type.charAt(0).toUpperCase() + nodeData.type.slice(1);
- typeEl.className = `info-value type-value ${nodeData.type}-type`;
-
- // Set category if it's a post
- const categoryContainer = document.getElementById(`${graphId}-info-category-container`);
+
+ // Get elements inside the panel
+ const infoTitleEl = document.getElementById(`${graphId}-info-title`);
+ const infoTypeEl = document.getElementById(`${graphId}-info-type`);
+ const categoryContainerEl = document.getElementById(`${graphId}-info-category-container`);
const categoryEl = document.getElementById(`${graphId}-info-category`);
-
- if (nodeData.type === 'post' && nodeData.category) {
- categoryContainer.style.display = 'block';
- categoryEl.textContent = nodeData.category;
-
- // Use category colors
- const catColor = predefinedColors[nodeData.category] || '#A0AEC0';
- categoryEl.style.backgroundColor = `${catColor}33`; // Add alpha
- categoryEl.style.color = catColor;
- } else {
- categoryContainer.style.display = 'none';
- }
-
- // Set tags
- const tagsContainer = document.getElementById(`${graphId}-info-tags-container`);
+ const tagsContainerEl = document.getElementById(`${graphId}-info-tags-container`);
const tagsEl = document.getElementById(`${graphId}-info-tags`);
-
- if (nodeData.type === 'post' && nodeData.tags && nodeData.tags.length > 0) {
- tagsContainer.style.display = 'block';
- tagsEl.innerHTML = '';
-
- nodeData.tags.forEach(tag => {
- const tagEl = document.createElement('span');
- tagEl.className = 'tag';
- tagEl.textContent = tag;
- tagEl.addEventListener('click', () => {
- // Try to find and highlight the tag node
- const tagNode = cyFullscreen.getElementById(`tag-${tag}`);
- if (tagNode.length > 0) {
- highlightNode(tagNode, cyFullscreen);
- showNodeInfo(tagNode.data());
- }
- });
- tagsEl.appendChild(tagEl);
- });
- } else {
- tagsContainer.style.display = 'none';
- }
-
- // Set connections
const connectionsEl = document.getElementById(`${graphId}-info-connections`);
- connectionsEl.innerHTML = '';
-
- const neighbors = cyFullscreen.getElementById(nodeData.id).neighborhood('node');
- if (neighbors.length > 0) {
- neighbors.forEach(neighbor => {
- const neighborData = neighbor.data();
- const li = document.createElement('li');
- const a = document.createElement('a');
- a.href = '#';
- a.textContent = neighborData.label;
- a.addEventListener('click', (e) => {
- e.preventDefault();
- highlightNode(neighbor, cyFullscreen);
- showNodeInfo(neighborData);
- });
- li.appendChild(a);
- connectionsEl.appendChild(li);
- });
- } else {
- const li = document.createElement('li');
- li.textContent = 'No connections';
- connectionsEl.appendChild(li);
- }
-
- // Set link based on node type
const linkEl = document.getElementById(`${graphId}-info-link`);
- if (nodeData.type === 'post') {
- linkEl.textContent = 'Read Post';
- linkEl.href = `/posts/${nodeData.id}/`;
- } else if (nodeData.type === 'tag') {
- linkEl.textContent = 'View Tag';
- linkEl.href = `/tag/${nodeData.label}/`;
- } else {
- linkEl.textContent = 'View Content';
- linkEl.href = '#';
+
+ // Set node title
+ if (infoTitleEl) infoTitleEl.textContent = nodeData.label;
+
+ // Set node type
+ if (infoTypeEl) {
+ infoTypeEl.textContent = nodeData.type.charAt(0).toUpperCase() + nodeData.type.slice(1);
+ infoTypeEl.className = `info-value type-value ${nodeData.type}-type`;
}
-
+
+ // Set category if it's a post
+ if (categoryContainerEl && categoryEl) {
+ if (nodeData.type === 'post' && nodeData.category) {
+ categoryContainerEl.style.display = 'block';
+ categoryEl.textContent = nodeData.category;
+ const catColor = predefinedColors[nodeData.category] || '#A0AEC0';
+ categoryEl.style.backgroundColor = `${catColor}33`;
+ categoryEl.style.color = catColor;
+ } else {
+ categoryContainerEl.style.display = 'none';
+ }
+ }
+
+ // Set tags
+ if (tagsContainerEl && tagsEl) {
+ if (nodeData.tags && nodeData.tags.length > 0) {
+ tagsContainerEl.style.display = 'block';
+ tagsEl.innerHTML = '';
+ nodeData.tags.forEach(tag => {
+ const tagEl = document.createElement('span');
+ tagEl.className = 'tag';
+ tagEl.textContent = tag;
+ tagEl.addEventListener('click', () => {
+ const tagNode = cyFullscreen.getElementById(`tag-${tag}`);
+ if (tagNode.length > 0) {
+ selectedNode = tagNode; // Update selected node
+ highlightNode(tagNode, cyFullscreen);
+ showNodeInfo(tagNode.data());
+ }
+ });
+ tagsEl.appendChild(tagEl);
+ });
+ } else {
+ tagsContainerEl.style.display = 'none';
+ }
+ }
+
+ // Set connections - now with interactive links
+ if (connectionsEl) {
+ connectionsEl.innerHTML = '';
+ const currentNode = cyFullscreen.getElementById(nodeData.id); // Get current node from fullscreen instance
+ const neighbors = currentNode.neighborhood('node');
+
+ if (neighbors.length > 0) {
+ neighbors.forEach(neighbor => {
+ const neighborData = neighbor.data();
+ const li = document.createElement('li');
+ const a = document.createElement('a');
+ a.href = '#';
+ a.textContent = neighborData.label;
+ a.addEventListener('click', (e) => {
+ e.preventDefault();
+ selectedNode = neighbor; // Update selected node
+ highlightNode(neighbor, cyFullscreen);
+ showNodeInfo(neighborData);
+ });
+ li.appendChild(a);
+ connectionsEl.appendChild(li);
+ });
+ } else {
+ const li = document.createElement('li');
+ li.textContent = 'No connections';
+ connectionsEl.appendChild(li);
+ }
+ }
+
+ // Set link based on node type - dynamic text and URL
+ if (linkEl) {
+ if (nodeData.type === 'post') {
+ linkEl.textContent = 'Read Post';
+ linkEl.href = nodeData.url || `/posts/${nodeData.id}/`;
+ } else if (nodeData.type === 'tag') {
+ linkEl.textContent = 'View Tag';
+ linkEl.href = nodeData.url || `/tag/${nodeData.label}/`;
+ } else {
+ linkEl.textContent = 'View Content';
+ linkEl.href = nodeData.url || '#';
+ }
+ }
+
// Show the panel
infoPanel.classList.add('active');
}
-
- // Make nodes clickable in mini graph
- cy.on('tap', 'node[type="tag"]', function(evt) {
- const node = evt.target;
- const tagName = node.data('label');
- window.location.href = `/tag/${tagName}`;
- });
- cy.on('tap', 'node[type="post"][level!=0]', function(evt) {
- const node = evt.target;
- const postSlug = node.id();
- window.location.href = `/posts/${postSlug}/`;
- });
} catch (error) {
console.error('[MiniGraph] Error initializing graph:', error);
container.innerHTML = 'Error loading graph
';