diff --git a/.eslintignore b/.eslintignore
index 74a5e2875..1ab1ee063 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -61,6 +61,8 @@ Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/langs/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
+CliClient/app/LinkSelector.js
+CliClient/build/LinkSelector.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
diff --git a/.gitignore b/.gitignore
index aa0282261..0a043d7e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,8 @@ Tools/commit_hook.txt
*.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
+CliClient/app/LinkSelector.js
+CliClient/build/LinkSelector.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
diff --git a/CliClient/app/LinkSelector.js b/CliClient/app/LinkSelector.ts
similarity index 79%
rename from CliClient/app/LinkSelector.js
rename to CliClient/app/LinkSelector.ts
index 285aa37ab..66e3dc780 100644
--- a/CliClient/app/LinkSelector.js
+++ b/CliClient/app/LinkSelector.ts
@@ -1,6 +1,19 @@
-const open = require('open');
+import open from 'open';
+
+interface LinkStoreEntry {
+ link: string;
+ noteX: number;
+ noteY: number;
+}
class LinkSelector {
+ noteId_: string;
+ scrollTop_: number;
+ renderedText_: string;
+ currentLinkIndex_: number;
+ linkStore_: LinkStoreEntry[];
+ linkRegex_: RegExp;
+
constructor() {
this.noteId_ = null;
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
@@ -10,27 +23,27 @@ class LinkSelector {
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
}
- get link() {
+ get link(): string | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].link;
}
- get noteX() {
+ get noteX(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteX;
}
- get noteY() {
+ get noteY(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteY;
}
- findLinks(renderedText) {
- const newLinkStore = [];
- const lines = renderedText.split('\n');
+ findLinks(renderedText: string): LinkStoreEntry[] {
+ const newLinkStore: LinkStoreEntry[] = [];
+ const lines: string[] = renderedText.split('\n');
for (let i = 0; i < lines.length; i++) {
const matches = [...lines[i].matchAll(this.linkRegex_)];
- matches.forEach((e, n) => {
+ matches.forEach((_e, n) => {
newLinkStore.push(
{
link: matches[n][0],
@@ -43,19 +56,19 @@ class LinkSelector {
return newLinkStore;
}
- updateText(renderedText) {
+ updateText(renderedText: string): void {
this.currentLinkIndex_ = null;
this.renderedText_ = renderedText;
this.linkStore_ = this.findLinks(this.renderedText_);
}
- updateNote(textWidget) {
+ updateNote(textWidget: any): void {
this.noteId_ = textWidget.noteId;
this.scrollTop_ = textWidget.scrollTop_;
this.updateText(textWidget.renderedText_);
}
- scrollWidget(textWidget) {
+ scrollWidget(textWidget: any): void {
if (this.currentLinkIndex_ === null) return;
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
@@ -80,7 +93,7 @@ class LinkSelector {
return;
}
- changeLink(textWidget, offset) {
+ changeLink(textWidget: any, offset: number): void | null {
if (textWidget.noteId !== this.noteId_) {
this.updateNote(textWidget);
this.changeLink(textWidget, offset);
@@ -110,13 +123,13 @@ class LinkSelector {
return;
}
- openLink(textWidget) {
+ openLink(textWidget: any): void {
if (textWidget.noteId !== this.noteId_) return;
if (textWidget.renderedText_ !== this.renderedText_) return;
if (textWidget.scrollTop_ !== this.scrollTop_) return;
open(this.linkStore_[this.currentLinkIndex_].link);
}
-
}
-module.exports = LinkSelector;
+export default LinkSelector;
+
diff --git a/CliClient/app/app-gui.js b/CliClient/app/app-gui.js
index 99ccbe57e..4b35d1ee0 100644
--- a/CliClient/app/app-gui.js
+++ b/CliClient/app/app-gui.js
@@ -33,7 +33,7 @@ const FolderListWidget = require('./gui/FolderListWidget.js');
const NoteListWidget = require('./gui/NoteListWidget.js');
const StatusBarWidget = require('./gui/StatusBarWidget.js');
const ConsoleWidget = require('./gui/ConsoleWidget.js');
-const LinkSelector = require('./LinkSelector.js');
+const LinkSelector = require('./LinkSelector.js').default;
class AppGui {
diff --git a/readme/terminal.md b/readme/terminal.md
index 0361f58f6..7d646933e 100644
--- a/readme/terminal.md
+++ b/readme/terminal.md
@@ -127,7 +127,7 @@ It is possible to also synchronise outside of the user interface by typing `jopl
# URLs
-When Ctrl+Clicking a URL, most terminals will open that URL in the default browser. However, one issue, especially with long URLs, is that they can end up like this:
+When Ctrl+Clicking a URL (or opening with the shortcut 'o' while it is highlighted), most terminals will open that URL in the default browser. However, one issue, especially with long URLs, is that they can end up like this:
@@ -137,7 +137,7 @@ As a solution Joplin tries to start a mini-server in the background and, if succ
-Since this is still an actual URL, the terminal will still make it clickable. And with shorter URLs, the text is more readable and the links unlikely to be cut. Both resources (files that are attached to notes) and external links are handled in this way.
+Since this is still an actual URL, the terminal will still make it click. And with shorter URLs, the text is more readable and the links unlikely to be cut. Both resources (files that are attached to notes) and external links are handled in this way.
# Attachments / Resources
@@ -204,6 +204,9 @@ As an example, this is the default keymap, but read below for a detailed explana
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
{ "keys": [" "], "command": "todo toggle $n" },
+ { "keys": ["n"], "type": "function", "command": "next_link" },
+ { "keys": ["b"], "type": "function", "command": "previous_link" },
+ { "keys": ["o"], "type": "function", "command": "open_link" },
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
@@ -222,7 +225,7 @@ Name | Description
`keys` | The array of keys that will trigger the action. Special keys such as page up, down arrow, etc. needs to be specified UPPERCASE. See the [list of available special keys](https://github.com/cronvel/terminal-kit/blob/3114206a9556f518cc63abbcb3d188fe1995100d/lib/termconfig/xterm.js#L531). For example, `['DELETE', 'BACKSPACE']` means the command will run if the user pressed either the delete or backspace key. Key combinations can also be provided - in that case specify them lowercase. For example "tc" means that the command will be executed when the user pressed "t" then "c". Special keys can also be used in this fashion - simply write them one after the other. For instance, `CTRL_WCTRL_W` means the action would be executed if the user pressed "ctrl-w ctrl-w".
`type` | The command type. It can have the value "exec", "function" or "prompt". **exec**: Simply execute the provided [command](#commands). For example `edit $n` would edit the selected note. **function**: Run a special commands (see below for the list of functions). **prompt**: A bit similar to "exec", except that the command is not going to be executed immediately - this allows the user to provide additional data. For example `mknote ""` would fill the command line with this command and allow the user to set the title. A prompt command can also take a `cursorPosition` parameter (see below)
`command` | The command that needs to be executed
-`cusorPosition` | An integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end. A value of "0" means positioning the caret at the first character. A value of "-1" means positioning it at the end.
+`cursorPosition` | An integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end. A value of "0" means positioning the caret at the first character. A value of "-1" means positioning it at the end.
This is the list of special functions:
@@ -235,6 +238,9 @@ move_up | Move up (in a list for example)
move_down | Move down (in a list for example)
page_up | Page up
page_down | Page down
+next_link | Select the next link in the currently opened note (the first link will be selected if no link is currently selected)
+previous_link | Select the previous link in the currently opened note (the last link will be selected if no link is currently selected)
+open_link | Open the currently selected link externally
activate | Activates the selected item. If the item is a note for example it will be open in the editor
delete | Deletes the selected item
toggle_console | Toggle the console
diff --git a/tsconfig.json b/tsconfig.json
index 5d6ecbb1c..f3c9b25c1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,7 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
+ "lib": ["es2020.string", "dom", "dom.iterable"],
"alwaysStrict": true,
"forceConsistentCasingInFileNames": true,
"listEmittedFiles": false,
@@ -13,6 +14,7 @@
"strictBindCallApply": true,
"strictFunctionTypes": true,
"sourceMap": true,
+ "esModuleInterop": true,
"jsx": "react",
},
"include": [