[mod] isolate beer

This commit is contained in:
Andy Bunce 2025-09-14 18:15:03 +01:00
parent 9dadee8b84
commit 03e30fb082
6 changed files with 446 additions and 2 deletions

30
package-lock.json generated
View file

@ -17,7 +17,8 @@
"@codemirror/theme-one-dark": "^6.1.0",
"@codemirror/view": "^6.38.1",
"ace-builds": "^1.43.2",
"ace-linters": "^1.8.3"
"ace-linters": "^1.8.3",
"beercss": "^3.12.3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1",
@ -672,6 +673,12 @@
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@material/material-color-utilities": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz",
"integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==",
"license": "Apache-2.0"
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
@ -1098,6 +1105,15 @@
"node": ">=0.4.0"
}
},
"node_modules/beercss": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/beercss/-/beercss-3.12.3.tgz",
"integrity": "sha512-t/rO4rbC+48aJe1pI6tMPNyBSLNNbHqmv4f3D8jZV9XBRKu0JVokpku8KeIQNzC1FAzYnW1t7/Yt2kPVjEp0yg==",
"license": "MIT",
"dependencies": {
"material-dynamic-colors": "^1.1.2"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -1371,6 +1387,18 @@
"node": ">= 18"
}
},
"node_modules/material-dynamic-colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/material-dynamic-colors/-/material-dynamic-colors-1.1.2.tgz",
"integrity": "sha512-8KD0jrPTFs2x06UJWbvkg6E0HyzFjrvS5oOc2DsXzIEOqZTbb3ruLMUhNuPSl8WeHA/O/RTAlTLcxqYXJzYwPA==",
"license": "MIT",
"dependencies": {
"@material/material-color-utilities": "^0.2.7"
},
"bin": {
"material-dynamic-colors": "cli.js"
}
},
"node_modules/minify": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/minify/-/minify-9.2.0.tgz",

View file

@ -12,7 +12,8 @@
"@codemirror/theme-one-dark": "^6.1.0",
"@codemirror/view": "^6.38.1",
"ace-builds": "^1.43.2",
"ace-linters": "^1.8.3"
"ace-linters": "^1.8.3",
"beercss": "^3.12.3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1",

View file

@ -0,0 +1,80 @@
.wrapper {
height: 100vh;
overflow: hidden;
display: grid;
grid-gap: 0;
grid-template-areas:
"header header"
"sidebar content"
"footer footer"
}
.sidebar {
grid-area: sidebar;
}
.content {
grid-area: content;
}
.header {
grid-area: header;
}
.footer {
grid-area: footer;
}
/* Set editor dimensions */
#editor {
height: 90%;
max-width: 100%;
overflow: hidden;
}
/* Stretch editor to fit inside its containing div */
.cm-editor {
height: 100%;
width: 100%;
overflow: auto;
}
.container {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
/* Tablet view */
@media (min-width: 768px) {
.main-layout {
grid-template-columns: 1fr 1fr;
/* Two columns for sidebars */
}
}
/* Desktop view */
@media (min-width: 1024px) {
.main-layout {
grid-template-columns: 200px 1fr 200px;
/* Fixed sidebars, fluid main */
}
}
body {
margin: 0;
font-family: 'Roboto', sans-serif;
}
.left-sidebar,
.right-sidebar {
background-color: var(--surface);
/* Use BeerCSS surface color */
}
main {
background-color: var(--background);
/* Main content background */
}

View file

@ -0,0 +1,152 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Beer example using BaseX LSP</title>
<link rel="icon" type="image/png" href="../favicon.png" />
<link href="../beercss-3.12.3/beer.min.css" rel="stylesheet" />
<link href="beer.css" rel="stylesheet" />
<script src="popover.js" defer></script>
</head>
<body>
<div class="wrapper">
<header class="header row tertiary">
<a id="help">XQuery 4.0 LSP client</a>
<button popovertarget="popHelp" class="circle transparent"><i>info</i></button>
<div class="max"></div>
<a class="nav-link active" aria-current="page" href="#">Editor</a>
<a class="nav-link" href="#">Msgs</a>
<a class="nav-link" href="/dba/logs" target="dba">Dba</a>
<div class="max"></div>
<button id="popcon" popovertarget="popConnect" class="circle transparent error">
<i>Cloud_Off</i>
</button>
</header>
<article class="sidebar">
<div id="msg">(msgs)</div>
<select id="load">
<option selected value="">load..</option>
<optgroup label="XQuery3">
<option
value="https://raw.githubusercontent.com/expkg-zone58/pdfbox/refs/heads/main/src/Pdfbox3.xqm">
Pdfbox3.xqm</option>
<option
value="https://raw.githubusercontent.com/Quodatum/xqdoca/refs/heads/master/src/main/lib/model.xqm">
model.xqm</option>
</optgroup>
<optgroup label="XQuery4">
<option
value="https://git.quodatum.duckdns.org/quodatum/basex-lsp/raw/branch/main/webapp/lsp/lsp-text.xqm">
lsp-text.xqm</option>
</optgroup>
<optgroup label="xpath">
<option
value="https://raw.githubusercontent.com/dnovatchev/Articles/refs/heads/main/Generators/Code/generator.xpath">
generator.xpath</option>
</optgroup>
</select>
<ul id="traffic" style="overflow: scroll;">
<li>-</li>
</ul>
</article>
<main class="content">
<div class="row top">
<nav class="group connected">
<label for="file">File:</label>
<input id="iFile" type="url" value="file:///some/file.xqm" />
<select id="language">
<option selected>Language</option>
<option value="plaintext">plaintext</option>
<option value="xquery">xquery</option>
<option value="xml">xml</option>
</select>
<label for="symbols">Symbols:</label><select id="symbols" disabled="disabled"></select>
</nav>
<nav class="group connected">
<button id="search" title="Search" type="button" class=""><i>Search</i></button>
<button id="lint" title="Diagnostics" type="button" class=""><i>Data_Alert</i></button>
<button id="sync" title="Sync changes to server" type="button" class="btn btn-light">
<i>Sync</i>
</button>
<button id="fullscreen" title="Full screen" type="button" class="btn btn-light">
<i>Fullscreen</i>
</button>
</nav>
<nav class="group connected">
<button id="syntax" type="button" class="btn btn-light" title="Syntax">
<i>Chat</i></button>
<button id="format" type="button" class="btn btn-light" title="Format (Shift-Alt-f)">
<i>Format_Align_Left</i></button>
<button id="cmd" type="button" class="btn btn-light" title="Cmd ">
<i>Order_Play</i>
</button>
<button type="button" class="btn btn-light" popovertarget="popSettings" title="Settings">
<i>Settings</i></button>
</nav>
</div>
<!-- Editor goes in here -->
<div id="editor"></div>
</main>
</div>
<!-- Popovers -->
<dialog id="popConnect" popover>
<header>Connect to LSP
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</header>
<div class="modal-body">
<div id="state">🔴</div>
<input id="iServer" type="text" style="width:25em" />
</div>
<div class="modal-footer">
<button id="connect">connect</button>
</div>
</dialog>
<popup-info id="popHelp">hhhh</popup-info>
<dialog id="popSettings" popover>
<header>Editor configuration
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
onclick="$('popSettings').hidePopover(); "></button>
</header>
<div class="modal-body">
@TODO
</div>
<div class="modal-footer">
<button id="connect">ok</button>
</div>
</dialog>
<!-- CodeMirror 6 -->
<script src="./lsp.bundle.js"></script>
<script type="module" src="../beercss-3.12.3/beer.min.js"></script>
<script src="./beer.js"></script>
</body>
</html>

View file

@ -0,0 +1,127 @@
const view = new lsp.EditorView({
extensions: lsp.baseExts,
parent: document.getElementById("editor")
});
let doc = "xquery version '3.1';\n(:~ comment:)\nmodule namespace pdfbox='ns';\n";
var client;
var extLint;
function $(id) { return document.getElementById(id) };
// Load saved content from localStorage when the page loads
window.addEventListener('load', () => {
const savedText = localStorage.getItem('code');
if (savedText) doc = savedText;
let svr = localStorage.getItem('lsp');
if (!svr) {
let x = new URL(window.location.href);
x.protocol = "ws";
x.pathname = "ws/lsp"
svr = x.href;
}
$("iServer").value = svr;
view.setState(lsp.EditorState.create({ doc: doc, extensions: lsp.baseExts }));
connect();
});
// Save content to localStorage when the page is about to unload
window.addEventListener('beforeunload', () => {
const doc = view.state.doc.toString();
localStorage.setItem('code', doc);
localStorage.setItem('lsp', $("iServer").value);
});
$("connect").onclick = e => { e.preventDefault(); connect() };
$("search").onclick = e => lsp.openSearchPanel(view);
$("fullscreen").onclick = e => $("editor").requestFullscreen();
$("cmd").onclick = e => console.log("CMDS", lsp.listCommands(view));
$("lint").onclick = async e => {
console.log("word", view.state.wordAt(1));
lsp.openLintPanel(view);
};
$("sync").onclick = e => { client.sync(); console.log("XXXsync"); };
$("format").onclick = e => {
console.log("FMT", lsp.formatDocument(view));
};
$("load").onchange = e => {
const url = e.target.value;
if (url.length == 0) return
fetch(url)
.then(response => response.text())
.then(t => {
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: t
}
})
//client.sync();
//console.log("SYNC");
});
$("load").value = "";
};
function connect() {
const server = $("iServer").value;
const file = $("iFile").value;
lsp.simpleWebSocketTransport(server)
.then(transport => {
transport.socket.onclose = (event) => connectStatus(false);
transport.socket.oneror = (event) => $("msg").innerText = "sock error!";
transport.subscribe(incoming);
client = new lsp.LSPClient({ extensions: lsp.languageServerExtensions() });
client.connect(transport);
$("popConnect").hidePopover();
connectStatus(true);
let extLsp = client.plugin(file, "xquery");
view.dispatch({
effects: lsp.StateEffect.appendConfig.of(
[lsp.linter(null, { autoPanel: true }), ...extLsp,
lsp.keymap.of([...lsp.formatKeymap])])
})
})
.catch(e => {
console.log(e);
connectStatus(false);
alert("connection failed: " + server)
});
};
function connectStatus(bool) {
if (bool) {
$("popcon").querySelector("i").innerText="cloud"
$("popcon").classList.remove("error")
$("popcon").classList.add("primary")
} else {
$("popcon").querySelector("i").innerText="cloud_off"
$("popcon").classList.add("error")
$("popcon").classList.remove("primary")
}
};
function incoming(msg) {
const rpc = JSON.parse(msg);
log(rpc);
};
function log(rpc) {
console.log("<-", rpc)
if (rpc.id) return
const text = rpc.method;
const li = document.createElement("li");
const n = $("traffic").childElementCount + " ";
li.appendChild(document.createTextNode(n + text));
$("traffic").insertBefore(li, $("traffic").firstChild)
};

View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holy Grail Layout with BeerCSS</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
body {
margin: 0;
font-family: 'Roboto', sans-serif;
}
.container {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.main-layout {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
@media (min-width: 768px) {
.main-layout {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 1024px) {
.main-layout {
grid-template-columns: 200px 1fr 200px;
}
}
@media (max-width: 767px) {
.main-layout {
grid-template-areas:
"main"
"left-sidebar"
"right-sidebar";
}
}
</style>
</head>
<body>
<div class="container">
<header class="primary padding large">Header</header>
<div class="main-layout">
<nav class="left-sidebar surface padding">Left Sidebar</nav>
<main class="padding">Main Content</main>
<aside class="right-sidebar surface padding">Right Sidebar</aside>
</div>
<footer class="primary padding large">Footer</footer>
</div>
</body>
</html>