kopher

A simple Gopher client for KaiOS
git clone git://git.luxferre.top/kopher.git
Log | Files | Refs | README | LICENSE

commit 5e4c21e30def8dfb37e28674be18d583b10f4110
parent 175fbe43126ab22144e54af99e94f35aaabc351e
Author: Luxferre <lux@ferre>
Date:   Sun, 26 Mar 2023 15:44:43 +0300

Improved sKai support and overall download algo

Diffstat:
MREADME.md | 25++++++++++++++++++++++++-
Mapp.css | 9+++++----
Mindex.html | 1+
Mjs/app.js | 25+++++++++++++++++++++++--
Mmanifest.webapp | 8+++++++-
5 files changed, 60 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md @@ -10,7 +10,23 @@ Structurally, Kopher is a KaiOS-specific frontend to the hi01379.js library whic ## Which KaiOS versions are supported? -Tested on 2.5 up to 2.5.4. May work on 1.0 (e.g. Alcatel Cingular Flip 2), definitely won't work on 3.x. +Tested on 2.5.x up to 2.5.4. Doesn't work on 1.0 (some bug with mozTCPSockets I'm not gonna investigate) and definitely won't work on 3.x. + +More specifically, as of now, Kopher has been tested on the following devices and KaiOS versions (ordered from oldest to newest): + +- Nokia 8110 4G v13 (2.5/GerdaOS) +- Nokia 8110 4G v17 (2.5.1) +- Doro 7060 (DFC-0190) (2.5.1)\*\* +- Sigma mobile S3500 sKai (2.5.1.1)\* +- Nokia 800 Tough (2.5.2) +- Nokia 2720 Flip (2.5.2.2) +- Crosscall Core-S4 (2.5.3.2) +- Nokia 8000 4G (2.5.4) + +*\* there is a bug with displaying Blob-type images on the sKai, so only the download option works correctly* +*\*\* install-via-FFBM trick; also, the system statusbar on the Doro is larger than usual, so the addressbar is a bit overlapped (doesn't hamper its readability)* + +N.B. If you want to debug Kopher to support KaiOS 1.0, make sure to replace the `'\u{1f310}'` reference in `js/app.js` with `'\uD83C\uDF10'` surrogate pair first. ## Which Gophermap entry types are supported? @@ -38,6 +54,7 @@ Note that currently, Kopher and hi01379.js assume that the search request from a - One-key switching the line wrapping mode between off (default) and on (for comfortable reading) - Unlimited navigation history (doesn't persist between sessions) - Up to 10 numbered bookmarks plus customizable homepage +- Binary blob downloads (see "Downloads") ## Controls @@ -61,6 +78,12 @@ Kopher's controls were inspired by old WAP browsers and partially Opera Mini. On - Version and page info: `# #` - Exit: End +## Downloads + +For image files supported by KaiOS, Kopher prompts whether or not you want to just view or store them, and if you choose to store them, they are saved to your default gallery storage. All other types are downloaded directly **to the root of your default storage** (the storage you selected in KaiOS setttings as the default media location, be it internal or the SD card). Note that Kopher does not do any heuristics on how the downloads folder should be named, nor does it create any by itself. Different phones handle the download storage place differently and B2G doesn't have a specific designator for it, so just using the root is the safest and most cross-device way of saving the files. + +Note that in most KaiOS phones the internal storage root isn't fully viewable with the stock file manager, so you might have to use MTP and/or third-party file managers like Explorer to see the files if this is the case with your device. For more consistent UX with downloads, please consider using an SD card as the default storage. + ## Wait... no input fields in the UI?! Exactly. No need to reinvent the wheel when the JS engine offers the `prompt()` method. You enter the starting address in the popup window, same way you enter search queries in the gopherholes. diff --git a/app.css b/app.css @@ -7,8 +7,8 @@ * {box-sizing: border-box; margin:0;padding:0} html, body { position: fixed; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; color: var(--fg-color); background: var(--bg-color); font-family: sans-serif @@ -19,8 +19,9 @@ body[data-theme="dark"] { --link-color: cyan; } -header {position: absolute; top: 0; left: 0; padding-left: 1px; width: 100%; height: 26px; border-bottom: 1px solid var(--fg-color); vertical-align: middle} -main {position: absolute; top: 26px; left: 0; width: 100%; height: calc(100% - 46px);font-size: 16px} +.preheader {position: absolute; top: 0; left: 0; width: 100%; height: 26px; background: #111} +header {position: absolute; top: 26px; left: 0; padding-left: 1px; width: 100%; height: 26px; border-bottom: 1px solid var(--fg-color); vertical-align: middle} +main {position: absolute; top: 52px; left: 0; width: 100%; height: calc(100% - 72px);font-size: 16px} footer {position: absolute; bottom: 0; left: 0; width: 100%; padding: 0 4px; height: 20px; border-top: 1px solid var(--fg-color); font-size: 14px} header>* {display:inline-block; font-size: 18px; padding: 2px; vertical-align: middle;line-height:22px} diff --git a/index.html b/index.html @@ -7,6 +7,7 @@ <link rel=stylesheet href="app.css"> </head> <body> + <div class="preheader"></div> <header><div class=logo>&#127760;</div><div class=addr></div></header> <main><div class=content data-wrap=0></div></main> <footer><div class=status>READY</div></footer> diff --git a/js/app.js b/js/app.js @@ -1,5 +1,20 @@ // Kopher UI logic +function saveBlob(blob, fname, successCb, errorCb) { + var storage = navigator.getDeviceStorage(blob.type.startsWith('image/') ? 'pictures' : 'sdcard'), + freeSpaceReq = storage.freeSpace() + freeSpaceReq.onsuccess = function() { + var freeSize = freeSpaceReq.result + if(freeSize > blob.size) { + var req = storage.addNamed(blob, fname) + req.onsuccess = successCb + req.onerror = errorCb + } + else errorCb(new Error('No free space available')) + } + freeSpaceReq.onerror = errorCb +} + function openURL(url, successCb, errorCb) { if(url.startsWith('about:')) { // handle about: pages first var pageName = url.split(':')[1], allowedPageNames = ['kopher', 'blank', 'help'] @@ -27,8 +42,14 @@ function openURL(url, successCb, errorCb) { if(resource.length > 4) input = resource[4] Hi01379.load(resource, input, function(res) { if(res.content && res.content instanceof Blob) { // handle the download here - successCb({content: null, serviceMsg: 'Downloaded ' + res.contentName, updateAddr: false}) - window.open(URL.createObjectURL(res.content)) + if(res.contentType.startsWith('image/') && confirm('View the image? (press Cancel to save it)')) { // try to open it in an external image viewer + window.open(URL.createObjectURL(res.content)) + successCb({content: null, serviceMsg: 'Viewed ' + res.contentName, updateAddr: false}) + } else // proceed with storing the file + saveBlob(res.content, res.contentName, function() { + successCb({content: null, serviceMsg: 'Saved ' + res.contentName, updateAddr: false}) + navigator.mozNotification.createNotification('File saved', res.contentName).show() + }, errorCb) } else successCb(res) // proceed to UI with non-downloads }, errorCb) diff --git a/manifest.webapp b/manifest.webapp @@ -1,5 +1,5 @@ { - "version": "0.0.1", + "version": "0.0.2", "name": "Kopher", "description": "A Gopher client for KaiOS", "launch_path": "/index.html", @@ -13,9 +13,15 @@ }, "type": "privileged", "origin": "app://kopher.luxferre.top", + "chrome": {"statusbar": "overlap"}, "permissions": { "tcp-socket": { "description": "To fetch Gopher resources" + }, + "device-storage:pictures": {"access": "readwrite"}, + "device-storage:sdcard": {"access": "readwrite"}, + "desktop-notification": { + "description": "To notify about downloaded files" } }, "installs_allowed_from": [