mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Display link to resources and display them via server.
This commit is contained in:
parent
3cb52a4107
commit
13b4f3f4ea
85
CliClient/app/ResourceServer.js
Normal file
85
CliClient/app/ResourceServer.js
Normal file
@ -0,0 +1,85 @@
|
||||
import { _ } from 'lib/locale.js'
|
||||
import { Logger } from 'lib/logger.js';
|
||||
import { Resource } from 'lib/models/resource.js';
|
||||
import { netUtils } from 'lib/net-utils.js'
|
||||
|
||||
const http = require("http");
|
||||
const urlParser = require("url");
|
||||
const enableServerDestroy = require('server-destroy');
|
||||
|
||||
class ResourceServer {
|
||||
|
||||
constructor() {
|
||||
this.server_ = null;
|
||||
this.logger_ = new Logger();
|
||||
this.port_ = null;
|
||||
}
|
||||
|
||||
setLogger(logger) {
|
||||
this.logger_ = logger;
|
||||
}
|
||||
|
||||
logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
baseUrl() {
|
||||
if (!this.port_) return '';
|
||||
return 'http://127.0.0.1:' + this.port_;
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
|
||||
if (!this.port_) {
|
||||
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
|
||||
return;
|
||||
}
|
||||
|
||||
this.server_ = http.createServer();
|
||||
|
||||
this.server_.on('request', async (request, response) => {
|
||||
|
||||
const writeResponse = (message) => {
|
||||
response.write(message);
|
||||
response.end();
|
||||
}
|
||||
|
||||
const url = urlParser.parse(request.url, true);
|
||||
let resourceId = url.pathname.split('/');
|
||||
if (resourceId.length < 2) {
|
||||
writeResponse('Error: could not get resource ID from path name: ' + url.pathname);
|
||||
return;
|
||||
}
|
||||
resourceId = resourceId[1];
|
||||
|
||||
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));
|
||||
} catch (error) {
|
||||
response.setHeader('Content-Type', 'text/plain');
|
||||
response.statusCode = 400;
|
||||
writeResponse('Error: could not retrieve resource: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.server_.on('error', (error) => {
|
||||
this.logger().error('Resource server:', error);
|
||||
});
|
||||
|
||||
this.server_.listen(this.port_);
|
||||
|
||||
enableServerDestroy(this.server_);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.server_) this.server_.destroy();
|
||||
this.server_ = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ResourceServer;
|
@ -23,6 +23,7 @@ const RootWidget = require('tkwidgets/RootWidget.js');
|
||||
const WindowWidget = require('tkwidgets/WindowWidget.js');
|
||||
|
||||
const NoteWidget = require('./gui/NoteWidget.js');
|
||||
const ResourceServer = require('./ResourceServer.js');
|
||||
const NoteMetadataWidget = require('./gui/NoteMetadataWidget.js');
|
||||
const FolderListWidget = require('./gui/FolderListWidget.js');
|
||||
const NoteListWidget = require('./gui/NoteListWidget.js');
|
||||
@ -229,6 +230,10 @@ class AppGui {
|
||||
this.rootWidget_.addChild(win1);
|
||||
}
|
||||
|
||||
addCommandToConsole(cmd) {
|
||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||
}
|
||||
|
||||
setupShortcuts() {
|
||||
const shortcuts = {};
|
||||
|
||||
@ -291,7 +296,7 @@ class AppGui {
|
||||
action: async () => {
|
||||
const cmd = await this.widget('statusBar').prompt();
|
||||
if (!cmd) return;
|
||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||
this.addCommandToConsole(cmd);
|
||||
await this.processCommand(cmd);
|
||||
},
|
||||
};
|
||||
@ -327,17 +332,17 @@ class AppGui {
|
||||
|
||||
shortcuts['nn'] = {
|
||||
description: _('Create a new note'),
|
||||
action: { type: 'prompt', initialText: 'mknote ' },
|
||||
action: { type: 'prompt', initialText: 'mknote ""', cursorPosition: -2 },
|
||||
}
|
||||
|
||||
shortcuts['nt'] = {
|
||||
description: _('Create a new todo'),
|
||||
action: { type: 'prompt', initialText: 'mktodo ' },
|
||||
action: { type: 'prompt', initialText: 'mktodo ""', cursorPosition: -2 },
|
||||
}
|
||||
|
||||
shortcuts['nb'] = {
|
||||
description: _('Create a new notebook'),
|
||||
action: { type: 'prompt', initialText: 'mkbook ' },
|
||||
action: { type: 'prompt', initialText: 'mkbook ""', cursorPosition: -2 },
|
||||
}
|
||||
|
||||
return shortcuts;
|
||||
@ -526,6 +531,11 @@ class AppGui {
|
||||
this.updateStatusBarMessage();
|
||||
}
|
||||
|
||||
exit() {
|
||||
this.fullScreen(false);
|
||||
this.resourceServer_.stop();
|
||||
}
|
||||
|
||||
updateStatusBarMessage() {
|
||||
const consoleWidget = this.widget('console');
|
||||
|
||||
@ -547,12 +557,58 @@ class AppGui {
|
||||
if (msg !== '') this.widget('statusBar').setItemAt(0, msg);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// By default, before the server is started, only the regular
|
||||
// URLs appear in blue.
|
||||
noteTextWidget.markdownRendererOptions = {
|
||||
linkUrlRenderer: (url) => {
|
||||
if (resourceIdRegex.test(url)) {
|
||||
return url;
|
||||
} else {
|
||||
return regularUrlRenderer(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);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
const term = this.term();
|
||||
|
||||
this.fullScreen();
|
||||
|
||||
try {
|
||||
this.setupResourceServer();
|
||||
|
||||
this.renderer_.start();
|
||||
|
||||
const statusBar = this.widget('statusBar');
|
||||
@ -630,8 +686,10 @@ class AppGui {
|
||||
await cmd.action();
|
||||
} else if (typeof cmd.action === 'object') {
|
||||
if (cmd.action.type === 'prompt') {
|
||||
const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '');
|
||||
this.stdout(commandString);
|
||||
let promptOptions = {};
|
||||
if ('cursorPosition' in cmd.action) promptOptions.cursorPosition = cmd.action.cursorPosition;
|
||||
const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '', null, promptOptions);
|
||||
this.addCommandToConsole(commandString);
|
||||
await this.processCommand(commandString);
|
||||
} else {
|
||||
throw new Error('Unknown command: ' + JSON.stringify(cmd.action));
|
||||
|
@ -315,9 +315,24 @@ class Application {
|
||||
}
|
||||
|
||||
async exit(code = 0) {
|
||||
await Setting.saveAll();
|
||||
this.gui().fullScreen(false);
|
||||
process.exit(code);
|
||||
const doExit = async () => {
|
||||
await Setting.saveAll();
|
||||
this.gui().exit();
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
// Give it a few seconds to cancel otherwise exit anyway
|
||||
setTimeout(async () => {
|
||||
await doExit();
|
||||
}, 5000);
|
||||
|
||||
if (await reg.syncStarted()) {
|
||||
this.stdout(_('Cancelling background synchronisation... Please wait.'));
|
||||
const sync = await reg.synchronizer(Setting.value('sync.target'));
|
||||
await sync.cancel();
|
||||
}
|
||||
|
||||
await doExit();
|
||||
}
|
||||
|
||||
commands() {
|
||||
@ -403,6 +418,7 @@ class Application {
|
||||
maximizeConsole: () => {},
|
||||
stdout: (text) => { console.info(text); },
|
||||
fullScreen: (b=true) => {},
|
||||
exit: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Command extends BaseCommand {
|
||||
|
||||
await Resource.save(resource, { isNew: true });
|
||||
|
||||
note.body += "\n" + Resource.markdownTag(resource);
|
||||
note.body += "\n\n" + Resource.markdownTag(resource);
|
||||
await Note.save(note);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ class FolderListWidget extends ListWidget {
|
||||
output.push(item.title);
|
||||
}
|
||||
|
||||
if (item && item.id) output.push(item.id.substr(0, 5));
|
||||
// if (item && item.id) output.push(item.id.substr(0, 5));
|
||||
|
||||
return output.join(' ');
|
||||
};
|
||||
|
@ -27,8 +27,10 @@ class StatusBarWidget extends BaseWidget {
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
async prompt(initialText = '', promptString = ':') {
|
||||
async prompt(initialText = '', promptString = null, options = null) {
|
||||
if (this.promptState_) throw new Error('Another prompt already active');
|
||||
if (promptString === null) promptString = ':';
|
||||
if (options === null) options = {};
|
||||
|
||||
this.root.globalDisableKeyboard(this);
|
||||
|
||||
@ -38,6 +40,8 @@ class StatusBarWidget extends BaseWidget {
|
||||
promptString: stripAnsi(promptString),
|
||||
};
|
||||
|
||||
if ('cursorPosition' in options) this.promptState_.cursorPosition = options.cursorPosition;
|
||||
|
||||
this.promptState_.promise = new Promise((resolve, reject) => {
|
||||
this.promptState_.resolve = resolve;
|
||||
this.promptState_.reject = reject;
|
||||
@ -100,6 +104,8 @@ class StatusBarWidget extends BaseWidget {
|
||||
style: this.term.innerStyle.bgBrightBlue.white, // NOTE: Need to use TK style for this as inputField is not compatible with chalk
|
||||
};
|
||||
|
||||
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
||||
|
||||
this.inputEventEmitter_ = this.term.inputField(options, (error, input) => {
|
||||
let resolveResult = null;
|
||||
const resolveFn = this.promptState_.resolve;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { _ } from 'lib/locale.js'
|
||||
import { netUtils } from 'lib/net-utils.js'
|
||||
|
||||
const tcpPortUsed = require('tcp-port-used');
|
||||
const http = require("http");
|
||||
const urlParser = require("url");
|
||||
const FormData = require('form-data');
|
||||
@ -43,16 +43,7 @@ class OneDriveApiNodeUtils {
|
||||
|
||||
this.api().setAuth(null);
|
||||
|
||||
let ports = this.possibleOAuthDancePorts();
|
||||
let port = null;
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
let inUse = await tcpPortUsed.check(ports[i]);
|
||||
if (!inUse) {
|
||||
port = ports[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const port = netUtils.findAvailablePort(this.possibleOAuthDancePorts());
|
||||
if (!port) throw new Error(_('All potential ports are in use - please report the issue at %s', 'https://github.com/laurent22/joplin'));
|
||||
|
||||
let authCodeUrl = this.api().authCodeUrl('http://localhost:' + port);
|
||||
|
@ -90,6 +90,9 @@ msgstr ""
|
||||
msgid "Exits the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancelling background synchronisation... Please wait."
|
||||
msgstr ""
|
||||
|
||||
msgid "Only Bash is currently supported for autocompletion."
|
||||
msgstr ""
|
||||
|
||||
|
@ -100,6 +100,10 @@ msgstr "Affiche l'aide pour la commande donnée."
|
||||
msgid "Exits the application."
|
||||
msgstr "Quitter le logiciel."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Cancelling background synchronisation... Please wait."
|
||||
msgstr "Annulation..."
|
||||
|
||||
msgid "Only Bash is currently supported for autocompletion."
|
||||
msgstr ""
|
||||
|
||||
|
@ -90,6 +90,9 @@ msgstr ""
|
||||
msgid "Exits the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancelling background synchronisation... Please wait."
|
||||
msgstr ""
|
||||
|
||||
msgid "Only Bash is currently supported for autocompletion."
|
||||
msgstr ""
|
||||
|
||||
|
@ -14,8 +14,8 @@ ajv@^4.9.1:
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
ajv@^5.1.0:
|
||||
version "5.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.4.tgz#3daf9a8b67221299fdae8d82d117ed8e6c80244b"
|
||||
version "5.2.5"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.5.tgz#b637234d3e2675eb5f79fc652242a853a48cb49f"
|
||||
dependencies:
|
||||
co "^4.6.0"
|
||||
fast-deep-equal "^1.0.0"
|
||||
@ -832,8 +832,8 @@ chalk@^1.1.3:
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.2.0.tgz#477b3bf2f9b8fd5ca9e429747e37f724ee7af240"
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
|
||||
dependencies:
|
||||
ansi-styles "^3.1.0"
|
||||
escape-string-regexp "^1.0.5"
|
||||
@ -1815,9 +1815,9 @@ node-bitmap@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/node-bitmap/-/node-bitmap-0.0.1.tgz#180eac7003e0c707618ef31368f62f84b2a69091"
|
||||
|
||||
"node-emoji@https://github.com/laurent22/node-emoji":
|
||||
"node-emoji@git+https://github.com/laurent22/node-emoji.git":
|
||||
version "1.8.1"
|
||||
resolved "https://github.com/laurent22/node-emoji#9fa01eac463e94dde1316ef8c53089eeef4973b5"
|
||||
resolved "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5"
|
||||
dependencies:
|
||||
lodash.toarray "^4.4.0"
|
||||
|
||||
@ -2455,8 +2455,8 @@ tcp-port-used@^0.1.2:
|
||||
q "0.9.7"
|
||||
|
||||
terminal-kit@^1.13.11:
|
||||
version "1.13.13"
|
||||
resolved "https://registry.yarnpkg.com/terminal-kit/-/terminal-kit-1.13.13.tgz#fa53dbc17f5fb7e1630784274a7e57a10b1c0201"
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/terminal-kit/-/terminal-kit-1.14.0.tgz#1cb89c22bf697152a3f8e54569c61d5084ed690a"
|
||||
dependencies:
|
||||
async-kit "^2.2.3"
|
||||
get-pixels "^3.3.0"
|
||||
|
@ -109,6 +109,15 @@ class BaseModel {
|
||||
return id.substr(0, 5);
|
||||
}
|
||||
|
||||
// static minimalPartialId(id) {
|
||||
// let length = 2;
|
||||
// while (true) {
|
||||
// const partialId = id.substr(0, length);
|
||||
// const r = await this.db().selectOne('SELECT count(*) as total FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
|
||||
// if (r['total'] <= 1) return partialId;
|
||||
// }
|
||||
// }
|
||||
|
||||
static loadByPartialId(partialId) {
|
||||
return this.modelSelectAll('SELECT * FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { mime } from 'lib/mime-utils.js';
|
||||
import { filename } from 'lib/path-utils.js';
|
||||
import { FsDriverDummy } from 'lib/fs-driver-dummy.js';
|
||||
import { markdownUtils } from 'lib/markdown-utils.js';
|
||||
import lodash from 'lodash';
|
||||
import lodash from 'lodash';
|
||||
|
||||
class Resource extends BaseItem {
|
||||
|
||||
@ -59,7 +59,7 @@ class Resource extends BaseItem {
|
||||
return filename(path);
|
||||
}
|
||||
|
||||
static content(resource) {
|
||||
static async content(resource) {
|
||||
return this.fsDriver().readFile(this.fullPath(resource));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { shim } from 'lib/shim.js'
|
||||
|
||||
const tcpPortUsed = require('tcp-port-used');
|
||||
|
||||
const netUtils = {};
|
||||
|
||||
netUtils.ip = async () => {
|
||||
@ -12,4 +14,20 @@ netUtils.ip = async () => {
|
||||
return ip.ip;
|
||||
}
|
||||
|
||||
netUtils.findAvailablePort = async (possiblePorts, extraRandomPortsToTry = 20) => {
|
||||
for (let i = 0; i < extraRandomPortsToTry; i++) {
|
||||
possiblePorts.push(Math.floor(8000 + Math.random() * 2000));
|
||||
}
|
||||
|
||||
let port = null;
|
||||
for (let i = 0; i < possiblePorts.length; i++) {
|
||||
let inUse = await tcpPortUsed.check(possiblePorts[i]);
|
||||
if (!inUse) {
|
||||
port = possiblePorts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
export { netUtils };
|
@ -14,6 +14,7 @@ import { _ } from 'lib/locale.js';
|
||||
const reg = {};
|
||||
|
||||
reg.initSynchronizerStates_ = {};
|
||||
reg.synchronizers_ = {};
|
||||
|
||||
reg.logger = () => {
|
||||
if (!reg.logger_) {
|
||||
@ -96,7 +97,6 @@ reg.initSynchronizer_ = async (syncTargetId) => {
|
||||
}
|
||||
|
||||
reg.synchronizer = async (syncTargetId) => {
|
||||
if (!reg.synchronizers_) reg.synchronizers_ = [];
|
||||
if (reg.synchronizers_[syncTargetId]) return reg.synchronizers_[syncTargetId];
|
||||
if (!reg.db()) throw new Error('Cannot initialize synchronizer: db not initialized');
|
||||
|
||||
@ -191,6 +191,7 @@ reg.scheduleSync = async (delay = null) => {
|
||||
|
||||
reg.syncStarted = async () => {
|
||||
const syncTarget = Setting.value('sync.target');
|
||||
if (!reg.synchronizers_[syncTarget]) return false;
|
||||
if (!reg.syncHasAuth(syncTarget)) return false;
|
||||
const sync = await reg.synchronizer(syncTarget);
|
||||
return sync.state() != 'idle';
|
||||
|
Loading…
Reference in New Issue
Block a user