From bd8926958f7a2b65c8b18f842cee87349b294c6c Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Thu, 26 Oct 2017 20:58:27 +0100 Subject: [PATCH] Improved handling of external links and resources --- CliClient/app/ResourceServer.js | 26 +++++++++--- CliClient/app/app-gui.js | 75 +++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/CliClient/app/ResourceServer.js b/CliClient/app/ResourceServer.js index 950a2f3fd4..5cbb1e1109 100644 --- a/CliClient/app/ResourceServer.js +++ b/CliClient/app/ResourceServer.js @@ -13,6 +13,8 @@ class ResourceServer { this.server_ = null; this.logger_ = new Logger(); this.port_ = null; + this.linkHandler_ = null; + this.started_ = false; } setLogger(logger) { @@ -23,11 +25,19 @@ class ResourceServer { return this.logger_; } + started() { + return this.started_; + } + baseUrl() { if (!this.port_) return ''; return 'http://127.0.0.1:' + this.port_; } + setLinkHandler(handler) { + this.linkHandler_ = handler; + } + async start() { this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]); if (!this.port_) { @@ -52,18 +62,18 @@ class ResourceServer { } resourceId = resourceId[1]; + if (!this.linkHandler_) throw new Error('No link handler is defined'); + try { - let resource = await Resource.loadByPartialId(resourceId); - if (!resource.length) throw new Error('No resource with ID ' + resourceId); - if (resource.length > 2) throw new Error('More than one resource match ID ' + resourceId); // That's very unlikely - resource = resource[0]; - if (resource.mime) response.setHeader('Content-Type', resource.mime); - writeResponse(await Resource.content(resource)); + const done = await this.linkHandler_(resourceId, response); + if (!done) throw new Error('Unhandled resource: ' + resourceId); } catch (error) { response.setHeader('Content-Type', 'text/plain'); response.statusCode = 400; - writeResponse('Error: could not retrieve resource: ' + error.message); + response.write(error.message); } + + response.end(); }); this.server_.on('error', (error) => { @@ -73,6 +83,8 @@ class ResourceServer { this.server_.listen(this.port_); enableServerDestroy(this.server_); + + this.started_ = true; } stop() { diff --git a/CliClient/app/app-gui.js b/CliClient/app/app-gui.js index 4ba058272d..02648ddcc8 100644 --- a/CliClient/app/app-gui.js +++ b/CliClient/app/app-gui.js @@ -3,6 +3,7 @@ import { Folder } from 'lib/models/folder.js'; import { Tag } from 'lib/models/tag.js'; import { BaseModel } from 'lib/base-model.js'; import { Note } from 'lib/models/note.js'; +import { Resource } from 'lib/models/resource.js'; import { cliUtils } from './cli-utils.js'; import { reducer, defaultState } from 'lib/reducer.js'; import { reg } from 'lib/registry.js'; @@ -46,10 +47,6 @@ class AppGui { this.renderer_ = new Renderer(this.term(), this.rootWidget_); - this.renderer_.on('renderDone', async (event) => { - //if (this.widget('console').hasFocus) this.widget('console').resetCursor(); - }); - this.app_.on('modelAction', async (event) => { await this.handleModelAction(event.action); }); @@ -609,48 +606,70 @@ class AppGui { if (msg !== '') this.widget('statusBar').setItemAt(0, msg); } - setupResourceServer() { + async setupResourceServer() { const linkStyle = chalk.blue.underline; const noteTextWidget = this.widget('noteText'); const resourceIdRegex = /^:\/[a-f0-9]+$/i - - const regularUrlRenderer = (url) => { - if (!url) return url; - const l = url.toLowerCase(); - if (l.indexOf('http://') === 0 || l.indexOf('https://') === 0 || l.indexOf('www.') === 0) { - return linkStyle(url); - } - return url; - } + const noteLinks = {}; // By default, before the server is started, only the regular // URLs appear in blue. noteTextWidget.markdownRendererOptions = { - linkUrlRenderer: (url) => { + linkUrlRenderer: (index, url) => { if (resourceIdRegex.test(url)) { return url; + } else if (url.indexOf('http://') === 0 || url.indexOf('https://') === 0) { + return linkStyle(url); } else { - return regularUrlRenderer(url); + return url; } }, }; this.resourceServer_ = new ResourceServer(); this.resourceServer_.setLogger(this.app().logger()); - this.resourceServer_.start().then(() => { - if (this.resourceServer_.baseUrl()) { - noteTextWidget.markdownRendererOptions = { - linkUrlRenderer: (url) => { - if (resourceIdRegex.test(url)) { - const resourceId = url.substr(2); - return linkStyle(this.resourceServer_.baseUrl() + '/' + resourceId.substr(0, 7)); - } else { - return regularUrlRenderer(url); - } - }, - }; + this.resourceServer_.setLinkHandler(async (path, response) => { + const link = noteLinks[path]; + + if (link.type === 'url') { + response.writeHead(302, { 'Location': link.url }); + return true; } + + if (link.type === 'resource') { + const resourceId = link.id; + let resource = await Resource.load(resourceId); + if (!resource) throw new Error('No resource with ID ' + resourceId); // Should be nearly impossible + if (resource.mime) response.setHeader('Content-Type', resource.mime); + response.write(await Resource.content(resource)); + return true; + } + + return false; }); + + await this.resourceServer_.start(); + if (!this.resourceServer_.started()) return; + + noteTextWidget.markdownRendererOptions = { + linkUrlRenderer: (index, url) => { + if (!url) return url; + + if (resourceIdRegex.test(url)) { + noteLinks[index] = { + type: 'resource', + id: url.substr(2), + }; + } else { + noteLinks[index] = { + type: 'url', + url: url, + }; + } + + return linkStyle(this.resourceServer_.baseUrl() + '/' + index); + }, + }; } async start() {