Add local Prism highlighting for C64 BASIC and ACME assembly
This commit is contained in:
parent
3dd4e077d2
commit
489918fbf8
7 changed files with 57 additions and 3 deletions
|
|
@ -6,6 +6,10 @@ RUN npm ci --omit=dev && mkdir /app/screenshot-cache && chown node:node /app/scr
|
||||||
COPY server.mjs ./server.mjs
|
COPY server.mjs ./server.mjs
|
||||||
COPY screenshot-cache.mjs ./screenshot-cache.mjs
|
COPY screenshot-cache.mjs ./screenshot-cache.mjs
|
||||||
COPY public ./public
|
COPY public ./public
|
||||||
|
RUN mkdir -p public/vendor/prism \
|
||||||
|
&& cp node_modules/prismjs/components/prism-core.min.js public/vendor/prism/ \
|
||||||
|
&& cp node_modules/prismjs/components/prism-asm6502.min.js public/vendor/prism/ \
|
||||||
|
&& cp node_modules/prismjs/LICENSE public/vendor/prism/LICENSE
|
||||||
COPY . /programs
|
COPY . /programs
|
||||||
|
|
||||||
ENV PORT=8080
|
ENV PORT=8080
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "c64-program-collection",
|
"name": "c64-program-collection",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"prismjs": "1.30.0",
|
||||||
"sharp": "0.35.5"
|
"sharp": "0.35.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -528,6 +529,15 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prismjs": {
|
||||||
|
"version": "1.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||||
|
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.8.5",
|
"version": "7.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
"name": "c64-program-collection",
|
"name": "c64-program-collection",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": { "sharp": "0.35.5" }
|
"dependencies": { "sharp": "0.35.5", "prismjs": "1.30.0" }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ async function showSource(program) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/source?path=${encodeURIComponent(program.path)}`, { cache: 'no-store' });
|
const response = await fetch(`/api/source?path=${encodeURIComponent(program.path)}`, { cache: 'no-store' });
|
||||||
if (!response.ok) throw new Error('The program source could not be loaded.');
|
if (!response.ok) throw new Error('The program source could not be loaded.');
|
||||||
sourceCode.querySelector('code').textContent = await response.text();
|
highlightSource(sourceCode.querySelector('code'), program, await response.text());
|
||||||
sourceStatus.hidden = true;
|
sourceStatus.hidden = true;
|
||||||
sourceCode.hidden = false;
|
sourceCode.hidden = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -273,7 +273,7 @@ async function loadInlineSource(program) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/source?path=${encodeURIComponent(program.path)}`, { cache: 'no-store' });
|
const response = await fetch(`/api/source?path=${encodeURIComponent(program.path)}`, { cache: 'no-store' });
|
||||||
if (!response.ok) throw new Error('The program source could not be loaded.');
|
if (!response.ok) throw new Error('The program source could not be loaded.');
|
||||||
listing.querySelector('code').textContent = await response.text();
|
highlightSource(listing.querySelector('code'), program, await response.text());
|
||||||
message.hidden = true;
|
message.hidden = true;
|
||||||
listing.hidden = false;
|
listing.hidden = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@
|
||||||
<p id="source-status" class="status">Loading source…</p>
|
<p id="source-status" class="status">Loading source…</p>
|
||||||
<pre id="source-code" hidden><code></code></pre>
|
<pre id="source-code" hidden><code></code></pre>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
<script src="/vendor/prism/prism-core.min.js" data-manual></script>
|
||||||
|
<script src="/vendor/prism/prism-asm6502.min.js"></script>
|
||||||
|
<script src="/source-highlighting.js"></script>
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
28
public/source-highlighting.js
Normal file
28
public/source-highlighting.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* Prism grammars tailored to the sources in this collection. */
|
||||||
|
if (window.Prism) {
|
||||||
|
Prism.languages['c64-basic'] = {
|
||||||
|
'string': { pattern: /"[^"\r\n]*(?:"|(?=\r?$))/m, greedy: true },
|
||||||
|
'comment': { pattern: /\bREM[^\r\n]*/i, greedy: true },
|
||||||
|
'line-number': { pattern: /^\d+/m, alias: 'number' },
|
||||||
|
'keyword': /\b(?:CLOSE|CLR|CMD|CONT|DATA|DEF|DIM|END|FN|FOR|GET|GOSUB|GOTO|IF|INPUT|LET|LIST|LOAD|NEW|NEXT|ON|OPEN|POKE|PRINT|READ|RESTORE|RETURN|RUN|SAVE|STEP|STOP|SYS|THEN|TO|VERIFY|WAIT)\b/i,
|
||||||
|
'function': /\b(?:ABS|ASC|ATN|CHR\$|COS|EXP|FRE|INT|LEFT\$|LEN|LOG|MID\$|PEEK|POS|RIGHT\$|RND|SGN|SIN|SPC|SQR|STR\$|TAB|TAN|USR|VAL)(?=\s*\()/i,
|
||||||
|
'number': /(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,
|
||||||
|
'operator': /[+\-*/^=<>]|\b(?:AND|NOT|OR)\b/i,
|
||||||
|
'punctuation': /[,;:()]/
|
||||||
|
};
|
||||||
|
Prism.languages['acme-6502'] = Prism.languages.extend('asm6502', {
|
||||||
|
'directive': { pattern: /!\w+\b|\.\w+(?=\s)/, alias: 'property' },
|
||||||
|
'operator': /[+\-*/=<>#&|^~]/
|
||||||
|
});
|
||||||
|
Prism.languages.insertBefore('acme-6502', 'op-code', {
|
||||||
|
'label': { pattern: /^\s*[a-z_]\w*(?=:)/im, alias: 'function' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightSource(element, program, source) {
|
||||||
|
const language = program.language === 'machine-code' ? 'acme-6502' : 'c64-basic';
|
||||||
|
element.className = `language-${language}`;
|
||||||
|
// Assign text before highlighting, so source cannot inject HTML.
|
||||||
|
element.textContent = source;
|
||||||
|
if (window.Prism) Prism.highlightElement(element);
|
||||||
|
}
|
||||||
|
|
@ -85,6 +85,15 @@ button:disabled { opacity: .5; cursor: wait; }
|
||||||
.instructions-panel h2, .program-source-panel h2 { margin: 0 0 .85rem; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; }
|
.instructions-panel h2, .program-source-panel h2 { margin: 0 0 .85rem; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; }
|
||||||
.program-source-panel pre { max-height: 600px; margin: 0; padding: 1rem; overflow: auto; border-radius: 6px; background: #0d0928; color: #d8d3f4; font: .85rem/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre; tab-size: 4; }
|
.program-source-panel pre { max-height: 600px; margin: 0; padding: 1rem; overflow: auto; border-radius: 6px; background: #0d0928; color: #d8d3f4; font: .85rem/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre; tab-size: 4; }
|
||||||
.program-source-panel pre:focus-visible { outline: 2px solid var(--c64-light-blue); outline-offset: 2px; }
|
.program-source-panel pre:focus-visible { outline: 2px solid var(--c64-light-blue); outline-offset: 2px; }
|
||||||
|
.program-source-panel code, .source-modal code { font: inherit; }
|
||||||
|
.token.comment { color: #aaa5b8; font-style: italic; }
|
||||||
|
.token.keyword { color: #c6b4ff; }
|
||||||
|
.token.string { color: #a6e3ad; }
|
||||||
|
.token.number { color: #f9c88b; }
|
||||||
|
.token.function { color: #8bd5ef; }
|
||||||
|
.token.property { color: #f5bde6; }
|
||||||
|
.token.variable { color: #eed49f; }
|
||||||
|
.token.operator, .token.punctuation { color: #cad3f5; }
|
||||||
.control-list { display: grid; gap: .7rem; }
|
.control-list { display: grid; gap: .7rem; }
|
||||||
.control-row { display: grid; grid-template-columns: minmax(150px, auto) 1fr; gap: 1rem; align-items: center; color: #d2cef0; }
|
.control-row { display: grid; grid-template-columns: minmax(150px, auto) 1fr; gap: 1rem; align-items: center; color: #d2cef0; }
|
||||||
.key-group { display: flex; flex-wrap: wrap; gap: .35rem; }
|
.key-group { display: flex; flex-wrap: wrap; gap: .35rem; }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue