From 7462be3464c0132e793063379305c7a727f7d96a Mon Sep 17 00:00:00 2001 From: frederic luchting Date: Wed, 18 Feb 2026 22:01:42 +0100 Subject: [PATCH 1/3] Nicer sparql examples --- NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml | 2 +- .../wwwroot/css/{sparql-help.css => sparql-examples.css} | 0 .../wwwroot/{sparql-help.html => sparql-examples.html} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename NulllogiconeCore/NulllogiconeCore/wwwroot/css/{sparql-help.css => sparql-examples.css} (100%) rename NulllogiconeCore/NulllogiconeCore/wwwroot/{sparql-help.html => sparql-examples.html} (99%) diff --git a/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml b/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml index 9e223a9..f0ee6e8 100644 --- a/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml +++ b/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml @@ -48,7 +48,7 @@ Click to open: /sparql/ui (in new tab)

- Open interactive query examples + Open interactive query examples with syntax highlighting and one-click copy functionality to get started quickly.

diff --git a/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-help.css b/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css similarity index 100% rename from NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-help.css rename to NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css diff --git a/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-help.html b/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html similarity index 99% rename from NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-help.html rename to NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html index 5a9c940..4ffec67 100644 --- a/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-help.html +++ b/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html @@ -4,7 +4,7 @@ SPARQL Query Examples - nulllogicone.net - + From 04c90286af02974896a5d111145d0a6b8a83e428 Mon Sep 17 00:00:00 2001 From: frederic luchting Date: Wed, 18 Feb 2026 22:43:58 +0100 Subject: [PATCH 2/3] Extract javascript for sparql examples page --- .../wwwroot/css/sparql-examples.css | 38 +++ .../wwwroot/js/sparql-examples.js | 250 ++++++++++++++++++ .../wwwroot/sparql-examples.html | 198 +------------- 3 files changed, 290 insertions(+), 196 deletions(-) create mode 100644 NulllogiconeCore/NulllogiconeCore/wwwroot/js/sparql-examples.js diff --git a/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css b/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css index 8ed3761..91e666d 100644 --- a/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css +++ b/NulllogiconeCore/NulllogiconeCore/wwwroot/css/sparql-examples.css @@ -117,6 +117,17 @@ header p { overflow-x: auto; white-space: pre; margin-bottom: 15px; + transition: background-color 0.3s ease; +} + +.query-code.flash-copy { + animation: flashBackground 0.6s ease; +} + +@keyframes flashBackground { + 0% { background: #f5f5f5; } + 50% { background: #d4edda; } + 100% { background: #f5f5f5; } } .copy-btn { @@ -219,3 +230,30 @@ footer a:hover { color: #008000; font-style: italic; } + +/* Toast notification */ +.toast { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%) translateY(20px); + background: rgba(0, 0, 0, 0.85); + color: white; + padding: 12px 24px; + border-radius: 6px; + font-size: 14px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + opacity: 0; + transition: all 0.3s ease; + pointer-events: none; + z-index: 1000; +} + +.toast.show { + opacity: 1; + transform: translateX(-50%) translateY(0); +} + +.toast.toast-error { + background: rgba(192, 0, 0, 0.9); +} diff --git a/NulllogiconeCore/NulllogiconeCore/wwwroot/js/sparql-examples.js b/NulllogiconeCore/NulllogiconeCore/wwwroot/js/sparql-examples.js new file mode 100644 index 0000000..4ecaeb0 --- /dev/null +++ b/NulllogiconeCore/NulllogiconeCore/wwwroot/js/sparql-examples.js @@ -0,0 +1,250 @@ +// SPARQL Examples Page JavaScript +// Handles interactive query display, copying, and syntax highlighting + +let allExpanded = false; +let mouseDownPos = null; + +// Standard prefixes to prepend when copying +const STANDARD_PREFIXES = `PREFIX nlo: +PREFIX entity: +PREFIX rdf: +PREFIX rdfs: +PREFIX xsd: + +`; + +async function loadQueries() { + const container = document.getElementById('queries-container'); + + try { + console.log('Fetching queries...'); + const response = await fetch('/example-queries.sparql'); + if (!response.ok) throw new Error('Failed to load queries'); + + console.log('Parsing queries...'); + const content = await response.text(); + const queries = parseQueries(content); + console.log('Found', queries.length, 'queries'); + + if (queries.length === 0) { + container.innerHTML = '

No queries found in the file.
'; + return; + } + + console.log('Rendering queries...'); + container.innerHTML = queries.map((query, index) => ` +
+
+

${query.title}

+ +
+
+
${highlightSPARQL(query.code)}
+
+
+ `).join(''); + + } catch (error) { + container.innerHTML = ` +
+ Error loading queries:
+ ${error.message} +
+ `; + } +} + +function parseQueries(content) { + const queries = []; + const lines = content.split('\n'); + let i = 0; + + // Skip header lines until we find first separator + while (i < lines.length && !lines[i].includes('========')) { + i++; + } + + while (i < lines.length) { + // Look for separator line + if (lines[i].includes('========')) { + i++; // Move to next line after separator + + // Next line should be the title + if (i < lines.length && lines[i].trim().startsWith('#')) { + const title = lines[i].replace(/^#\s*\d*\.?\s*/, '').trim(); + i++; // Move past title + + // Skip the closing separator line + if (i < lines.length && lines[i].includes('========')) { + i++; + } + + // Collect code lines until next separator or end + const codeLines = []; + while (i < lines.length && !lines[i].includes('========')) { + codeLines.push(lines[i]); + i++; + } + + let code = codeLines.join('\n').trim(); + + // Remove PREFIX declarations for display (they'll be added back on copy) + code = code.split('\n') + .filter(line => !line.trim().toUpperCase().startsWith('PREFIX')) + .join('\n') + .trim(); + + if (code && title) { + queries.push({ title, code }); + } + } + } else { + i++; + } + } + + return queries; +} + +function highlightSPARQL(code) { + // Basic SPARQL syntax highlighting + let highlighted = code + .replace(//g, '>'); + + // Highlight keywords + const keywords = ['SELECT', 'WHERE', 'PREFIX', 'FILTER', 'OPTIONAL', 'LIMIT', 'ORDER BY', 'GROUP BY', 'COUNT', 'AS', 'CONSTRUCT', 'ASK', 'DESCRIBE', 'FROM', 'UNION', 'DISTINCT', 'REDUCED']; + keywords.forEach(keyword => { + const regex = new RegExp(`\\b${keyword}\\b`, 'gi'); + highlighted = highlighted.replace(regex, `${keyword.toUpperCase()}`); + }); + + // Highlight variables + highlighted = highlighted.replace(/(\?\w+)/g, '$1'); + + // Highlight URIs + highlighted = highlighted.replace(/(<[^&]+>)/g, '$1'); + + // Highlight prefixes (nlo:, rdf:, etc.) + highlighted = highlighted.replace(/(\w+:)/g, '$1'); + + // Highlight comments + highlighted = highlighted.replace(/(#[^\n]*)/g, '$1'); + + return highlighted; +} + +function toggleQuery(index) { + const body = document.getElementById(`body-${index}`); + const icon = document.getElementById(`icon-${index}`); + + body.classList.toggle('expanded'); + icon.classList.toggle('rotated'); +} + +function toggleAllQueries() { + const bodies = document.querySelectorAll('.query-body'); + const icons = document.querySelectorAll('.toggle-icon'); + const button = document.querySelector('.expand-all'); + + allExpanded = !allExpanded; + + bodies.forEach(body => { + if (allExpanded) { + body.classList.add('expanded'); + } else { + body.classList.remove('expanded'); + } + }); + + icons.forEach(icon => { + if (allExpanded) { + icon.classList.add('rotated'); + } else { + icon.classList.remove('rotated'); + } + }); + + button.textContent = allExpanded ? 'Collapse All Queries' : 'Expand All Queries'; +} + +function handleQueryMouseDown(event, index) { + // Record mouse position on mouse down + mouseDownPos = { x: event.clientX, y: event.clientY }; +} + +async function handleQueryMouseUp(event, index) { + // Check if mouse moved significantly (user was selecting text) + if (mouseDownPos) { + const moved = Math.abs(event.clientX - mouseDownPos.x) > 5 || + Math.abs(event.clientY - mouseDownPos.y) > 5; + mouseDownPos = null; + + // If mouse moved, user was selecting text, don't copy + if (moved) { + return; + } + + // Check if there's a text selection + const selection = window.getSelection(); + if (selection && selection.toString().length > 0) { + return; + } + } + + // Simple click detected - copy the query + await copyQueryWithToast(index); +} + +async function copyQueryWithToast(index) { + const codeElement = document.getElementById(`code-${index}`); + + // Add flash animation + codeElement.classList.add('flash-copy'); + setTimeout(() => { + codeElement.classList.remove('flash-copy'); + }, 600); + + // Get the plain text (without HTML formatting) + const queryText = codeElement.innerText; + + // Prepend standard prefixes to the query + const textToCopy = STANDARD_PREFIXES + queryText; + + try { + await navigator.clipboard.writeText(textToCopy); + showToast('✓ Copied to clipboard with prefixes!'); + } catch (err) { + showToast('✗ Failed to copy to clipboard', true); + } +} + +function showToast(message, isError = false) { + // Remove any existing toast + const existingToast = document.querySelector('.toast'); + if (existingToast) { + existingToast.remove(); + } + + // Create toast element + const toast = document.createElement('div'); + toast.className = 'toast' + (isError ? ' toast-error' : ''); + toast.textContent = message; + document.body.appendChild(toast); + + // Trigger animation + setTimeout(() => { + toast.classList.add('show'); + }, 10); + + // Remove after animation + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => { + toast.remove(); + }, 300); + }, 2000); +} + +// Load queries when page loads +document.addEventListener('DOMContentLoaded', loadQueries); diff --git a/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html b/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html index 4ffec67..a905877 100644 --- a/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html +++ b/NulllogiconeCore/NulllogiconeCore/wwwroot/sparql-examples.html @@ -23,7 +23,7 @@

Welcome to the SPARQL Query Interface

- Click on any query below to expand it, then use the "Copy" button to copy the query to your clipboard. + Click on any query below to expand it, then click the query text to copy it to your clipboard. Note: Standard PREFIX declarations are automatically included when copied, even though they're hidden for readability. You can also download the raw queries: example-queries.sparql

@@ -47,201 +47,7 @@

Welcome to the SPARQL Query Interface

- + From f2eb6de3602504d7f2ab25edb851b977c8100009 Mon Sep 17 00:00:00 2001 From: frederic luchting Date: Wed, 18 Feb 2026 23:44:02 +0100 Subject: [PATCH 3/3] clean up --- .../NulllogiconeCore/Pages/Index.cshtml | 44 ++----------------- .../Pages/Shared/_Header.cshtml | 9 ++-- 2 files changed, 7 insertions(+), 46 deletions(-) diff --git a/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml b/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml index f0ee6e8..9189526 100644 --- a/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml +++ b/NulllogiconeCore/NulllogiconeCore/Pages/Index.cshtml @@ -51,21 +51,6 @@ Open interactive query examples with syntax highlighting and one-click copy functionality to get started quickly.

-

- Example SPARQL query to retrieve Stamm entities: -

-
PREFIX nlo: <http://nulllogicone.net/schema.rdfs#>
-SELECT ?stamm ?name ?email
-WHERE {
-  ?stamm a nlo:Stamm ;
-         nlo:name ?name ;
-         nlo:email ?email .
-}
-LIMIT 10
-

- Visit /sparql for programmatic access and usage instructions, or use the - SPARQL UI for interactive queries. -

Examples

Html

@@ -85,7 +70,7 @@ LIMIT 10 https://nulllogicone.net/Netz/76035f19-f4ae-4d58-a388-4bbc72c51cef

-

Json Examples

+

Json

https://nulllogicone.net/Stamm/b4111e0e-48d9-42c4-a6f6-ec4991264947.json
https://nulllogicone.net/Angler/be279cca-b934-45e6-85fd-96b1a6b1e6ed.json
@@ -93,7 +78,7 @@ LIMIT 10 https://nulllogicone.net/TopLab/0c373672-6b2d-46bf-8962-9f697f6722d3.json

-

RDF examples

+

RDF

https://nulllogicone.net/Stamm/b4111e0e-48d9-42c4-a6f6-ec4991264947.rdf
https://nulllogicone.net/Angler/be279cca-b934-45e6-85fd-96b1a6b1e6ed.rdf
@@ -110,17 +95,6 @@ LIMIT 10 you must specify the full path https://nulllogicone.net/schema.rdfs.

-

- For storage in local databases or XML files, - XSD documents are available as schema definitions for the - entities with their fields and the definition of the table structure with their relational - relationships. If they are used against web services, the types are bindingly defined - in the wsdl files. -

-

- For setting up a relational table structure, there are SQL scripts which - can be tailored to any use case. -

Documents

@@ -147,20 +121,10 @@ LIMIT 10

-

Web Applications

+

Web Application

diff --git a/NulllogiconeCore/NulllogiconeCore/Pages/Shared/_Header.cshtml b/NulllogiconeCore/NulllogiconeCore/Pages/Shared/_Header.cshtml index d9678c1..3507e71 100644 --- a/NulllogiconeCore/NulllogiconeCore/Pages/Shared/_Header.cshtml +++ b/NulllogiconeCore/NulllogiconeCore/Pages/Shared/_Header.cshtml @@ -1,6 +1,6 @@ @* Shared header partial: includes site banner and navigation *@
-@*