From 1bbe1ffd8172cc3678ddf14355f77a0a6aaee42d Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 3 Apr 2023 12:37:10 +0300 Subject: [PATCH] Added basic terminal coloring to the infolines --- README.md | 13 ++++++++-- app.css | 51 +++++++++++++++++++++++++++++++++++++ js/hi01379.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++- manifest.webapp | 2 +- 4 files changed, 130 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 63caee8..7a8775a 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Note that currently, Kopher and hi01379.js assume that the search request from a - Unlimited navigation history (doesn't persist between sessions) - Up to 10 numbered bookmarks plus customizable homepage - Binary blob downloads (see "Downloads") +- Limited support of ANSI terminal escape codes in Gophermap infolines (see "Does it have terminal emulation support for character styling?") ## Controls @@ -105,9 +106,17 @@ For your convenience, only the current hole hostname is always displayed in the Yes. In fact, UTF-8 is the only supported encoding. As ASCII is a subset of UTF-8, old ASCII-only resources will be displayed just fine as well. -## Does it have terminal emulation support for character coloring purposes? +## Does it have terminal emulation support for character styling? -No, but that feature is considered to be implemented if this client gains more traction and if there still is enough amount of the resources that actually use VT escape characters. +As of April 2023, Kopher implements a subset of ANSI terminal escape codes for text styling purposes with the following limitations: + +- they are applied to the Gophermap information lines **only** (and detected and deleted everywhere else); +- they only work within the single infoline or a block of sequential infolines and all styling gets reset when another entry type is encountered, even if the reset sequence isn't there; +- only the basic 8/16-color palette is implemented (because these codes physically translate into corresponding data attributes); +- beside the colors, the following text attributes are implemented: bold, italic, underline, blinking, inverse, hidden, strikethrough; +- any unsupported escape sequences are automatically deleted from the rendered output. + +All these limitations are intended to keep Kopher's codebase simple, and not likely to change in the foreseeable future. ## Can I use the hi01379.js library in non-KaiOS environments? diff --git a/app.css b/app.css index c2afc77..dcaea19 100644 --- a/app.css +++ b/app.css @@ -64,3 +64,54 @@ footer .status {width: 100%; text-overflow: ellipsis; overflow: hidden; white-sp } .content a[data-focused="1"] {text-decoration: underline} +/* Terminal infoline styling */ + +/* modes = ['', 'BB', 'FN', 'IT', 'UL', 'BL', '', 'IN', 'HI', 'ST'] */ + +/* colors (FC/BC): 0 black, 1 red, 2 green, 3 yellow, 4 blue, 5 magenta, 6 cyan, 7 white */ + +.content span[data-styling] { + --ds-fg-color: var(--fg-color); + --ds-bg-color: var(--bg-color); + background: var(--ds-bg-color); + color: var(--ds-fg-color); +} + +@keyframes strict_blink { + 0% { + visibility: hidden + } + 50% { + visibility: hidden + } + 100% { + visibility: visible + } +} + +.content span[data-styling~="BB"] {font-weight: bolder} +.content span[data-styling~="IT"] {font-style: italic} +.content span[data-styling~="UL"] {text-decoration: underline} +.content span[data-styling~="BL"] {animation: 2s linear infinite strict_blink} +.content span[data-styling~="IN"] {background:var(--ds-fg-color);color:var(--ds-bg-color)} +.content span[data-styling~="HI"] {visibility: hidden} +.content span[data-styling~="ST"] {text-decoration: line-through} + +.content span[data-styling~="FC0"] {--ds-fg-color: black} +.content span[data-styling~="FC1"] {--ds-fg-color: red} +.content span[data-styling~="FC2"] {--ds-fg-color: green} +.content span[data-styling~="FC3"] {--ds-fg-color: yellow} +.content span[data-styling~="FC4"] {--ds-fg-color: blue} +.content span[data-styling~="FC5"] {--ds-fg-color: magenta} +.content span[data-styling~="FC6"] {--ds-fg-color: cyan} +.content span[data-styling~="FC7"] {--ds-fg-color: white} + +.content span[data-styling~="BC0"] {--ds-bg-color: black} +.content span[data-styling~="BC1"] {--ds-bg-color: red} +.content span[data-styling~="BC2"] {--ds-bg-color: green} +.content span[data-styling~="BC3"] {--ds-bg-color: yellow} +.content span[data-styling~="BC4"] {--ds-bg-color: blue} +.content span[data-styling~="BC5"] {--ds-bg-color: magenta} +.content span[data-styling~="BC6"] {--ds-bg-color: cyan} +.content span[data-styling~="BC7"] {--ds-bg-color: white} + diff --git a/js/hi01379.js b/js/hi01379.js index 86d21ec..94873d4 100644 --- a/js/hi01379.js +++ b/js/hi01379.js @@ -28,11 +28,74 @@ Hi01379 = (function(psGopherRequest) { }).join('\n') } + function terminalize(str, attrs) { // partial terminal styling support with ANSI codes + if(!attrs) attrs = [] // accept previous attributes array if present + var prevattrs = attrs.join(' ') // preserve stringified copy + var out = '', c, styleseq, sl, l = str.length, i, j, att, attindex, strattrs, + visualOpenTag='', visualCloseTag='', + modes = ['', 'BB', 'FN', 'IT', 'UL', 'BL', '', 'IN', 'HI', 'ST'] + for(i=0;i= 30 && att <= 37) attrs.push('FC'+(att-30)) // foreground color + else if(att >= 40 && att <= 47) attrs.push('BC'+(att-40)) // background color + else if(att < 10) attrs.push(modes[att]) // activate special modes + else if(att > 20 && att < 30) { // deactivate special modes + if(att === 22) { // also deactivate bold mode 1 + attindex = attrs.indexOf('BB') + if(attindex > -1) + attrs.splice(attindex, 1) + } + attindex = attrs.indexOf(modes[att - 20]) + if(attindex > -1) + attrs.splice(attindex, 1) + } + else if(att === 39) { // reset only the foreground color + attrs = attrs.map(function(v) { + return v.startsWith('FC') ? '' : v + }).filter(String) + } + else if(att === 49) { // reset only the background color + attrs = attrs.map(function(v) { + return v.startsWith('BC') ? '' : v + }).filter(String) + } + } + // now we have our attrs array with all styling applied, output the tags + strattrs = attrs.join(' ') + if(strattrs != prevattrs) { // there are some changes + if(prevattrs != '') // previous attributes were not empty + out += visualCloseTag + if(strattrs) // current attributes are not empty + out += visualOpenTag.replace('%s', strattrs) + prevattrs = strattrs // update the previous attribute cache + } + } // don't add any processed characters "as is" to the output + else continue // skip the escape and proceed with the loop + } + else out += c // just append it to the output + } + return {output: out, attrs: attrs} // return both output and remaining attributes + } + function gophermapToHTML(gmap, chost, cport) { if(!cport) cport = 70 if(!chost) chost = 'localhost' var lines = gmap.split(/\r?\n/), line, l = lines.length, i, lbr = '
', type, rest, desc, sel, host, port, + iattrs = [], istyled, // attribute cache and styled infomessage placeholder output = '', pretag = 'pre', preflag = false for(i=0;i' } - output += desc + '\n' + istyled = terminalize(desc, iattrs) + iattrs = istyled.attrs + output += istyled.output + '\n' } else { //information messages ended, stop preformatting if(preflag) { preflag = false + iattrs = [] // reset info styling attributes output += '' } if(desc = desc.trim()) { // ignore empty descriptions diff --git a/manifest.webapp b/manifest.webapp index ee9ffbd..0d1515a 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -1,5 +1,5 @@ { - "version": "0.0.4", + "version": "0.0.5", "name": "Kopher", "description": "A Gopher client for KaiOS", "launch_path": "/index.html",