1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-29 22:48:10 +02:00

All: Resolves #134: Allow linking to a note from another note

This commit is contained in:
Laurent Cozic
2018-05-02 15:13:20 +01:00
parent ff1ee1249b
commit a419bc7253
9 changed files with 125 additions and 49 deletions

View File

@@ -14,7 +14,6 @@ class MdToHtml {
constructor(options = null) {
if (!options) options = {};
this.supportsResourceLinks_ = !!options.supportsResourceLinks;
this.loadedResources_ = {};
this.cachedContent_ = null;
this.cachedContentKey_ = null;
@@ -132,40 +131,27 @@ class MdToHtml {
const isResourceUrl = Resource.isResourceUrl(href);
const title = isResourceUrl ? this.getAttr_(attrs, 'title') : href;
if (isResourceUrl && !this.supportsResourceLinks_) {
// In mobile, links to local resources, such as PDF, etc. currently aren't supported.
// Ideally they should be opened in the user's browser.
return '<span style="opacity: 0.5">(Resource not yet supported: ';
let resourceIdAttr = "";
let icon = "";
let hrefAttr = '#';
if (isResourceUrl) {
const resourceId = Resource.pathToId(href);
href = "joplin://" + resourceId;
resourceIdAttr = "data-resource-id='" + resourceId + "'";
icon = '<span class="resource-icon"></span>';
} else {
let resourceIdAttr = "";
let icon = "";
let hrefAttr = '#';
if (isResourceUrl) {
const resourceId = Resource.pathToId(href);
href = "joplin://" + resourceId;
resourceIdAttr = "data-resource-id='" + resourceId + "'";
icon = '<span class="resource-icon"></span>';
} else {
// If the link is a plain URL (as opposed to a resource link), set the href to the actual
// link. This allows the link to be exported too when exporting to PDF.
hrefAttr = href;
}
const js = options.postMessageSyntax + "(" + JSON.stringify(href) + "); return false;";
let output = "<a " + resourceIdAttr + " title='" + htmlentities(title) + "' href='" + hrefAttr + "' onclick='" + js + "'>" + icon;
return output;
// If the link is a plain URL (as opposed to a resource link), set the href to the actual
// link. This allows the link to be exported too when exporting to PDF.
hrefAttr = href;
}
const js = options.postMessageSyntax + "(" + JSON.stringify(href) + "); return false;";
let output = "<a " + resourceIdAttr + " title='" + htmlentities(title) + "' href='" + hrefAttr + "' onclick='" + js + "'>" + icon;
return output;
}
renderCloseLink_(attrs, options) {
const href = this.getAttr_(attrs, 'href');
const isResourceUrl = Resource.isResourceUrl(href);
if (isResourceUrl && !this.supportsResourceLinks_) {
return ')</span>';
} else {
return '</a>';
}
return '</a>';
}
rendererPlugin_(language) {

View File

@@ -1,5 +1,5 @@
const React = require('react'); const Component = React.Component;
const { Platform, WebView, View, Linking } = require('react-native');
const { Platform, WebView, View } = require('react-native');
const { globalStyle } = require('lib/components/global-style.js');
const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting.js');
@@ -19,7 +19,7 @@ class NoteBodyViewer extends Component {
}
UNSAFE_componentWillMount() {
this.mdToHtml_ = new MdToHtml({ supportsResourceLinks: false });
this.mdToHtml_ = new MdToHtml();
this.isMounted_ = true;
}
@@ -115,7 +115,7 @@ class NoteBodyViewer extends Component {
//msg = msg.split(':');
//this.bodyScrollTop_ = Number(msg[1]);
} else {
Linking.openURL(msg);
this.props.onJoplinLinkClick(msg);
}
}}
/>

View File

@@ -1,9 +1,10 @@
const React = require('react'); const Component = React.Component;
const { Platform, Keyboard, BackHandler, View, Button, TextInput, WebView, Text, StyleSheet, Linking, Image } = require('react-native');
const { Platform, Clipboard, Keyboard, BackHandler, View, Button, TextInput, WebView, Text, StyleSheet, Linking, Image } = require('react-native');
const { connect } = require('react-redux');
const { uuid } = require('lib/uuid.js');
const RNFS = require('react-native-fs');
const Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
const Setting = require('lib/models/Setting.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
@@ -22,8 +23,8 @@ const { _ } = require('lib/locale.js');
const { reg } = require('lib/registry.js');
const { shim } = require('lib/shim.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { dialogs } = require('lib/dialogs.js');
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
const { NoteBodyViewer } = require('lib/components/note-body-viewer.js');
const RNFetchBlob = require('react-native-fetch-blob').default;
@@ -107,6 +108,39 @@ class NoteScreenComponent extends BaseScreenComponent {
this.noteTagDialog_closeRequested = () => {
this.setState({ noteTagDialogShown: false });
}
this.onJoplinLinkClick_ = async (msg) => {
try {
if (msg.indexOf('joplin://') === 0) {
const itemId = msg.substr('joplin://'.length);
const item = await BaseItem.loadItemById(itemId);
if (!item) throw new Error(_('No item with ID %s', itemId));
if (item.type_ === BaseModel.TYPE_NOTE) {
// Easier to just go back, then go to the note since
// the Note screen doesn't handle reloading a different note
this.props.dispatch({
type: 'NAV_BACK',
});
setTimeout(() => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: item.id,
});
}, 5);
} else {
throw new Error(_('The Joplin mobile app does not currently support this type of link: %s', BaseModel.modelTypeToName(item.type_)));
}
} else {
Linking.openURL(msg);
}
} catch (error) {
dialogs.error(this, error.message);
}
}
}
styles() {
@@ -402,6 +436,11 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
copyMarkdownLink_onPress() {
const note = this.state.note;
Clipboard.setString(Note.markdownTag(note));
}
menuOptions() {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
@@ -425,6 +464,7 @@ class NoteScreenComponent extends BaseScreenComponent {
if (isSaved) output.push({ title: _('Tags'), onPress: () => { this.tags_onPress(); } });
output.push({ title: isTodo ? _('Convert to note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } });
if (isSaved) output.push({ title: _('Copy Markdown link'), onPress: () => { this.copyMarkdownLink_onPress(); } });
output.push({ isDivider: true });
if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } });
output.push({ title: _('View on map'), onPress: () => { this.showOnMap_onPress(); } });
@@ -466,7 +506,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.saveOneProperty('body', newBody);
};
bodyComponent = <NoteBodyViewer style={this.styles().noteBodyViewer} webViewStyle={theme} note={note} onCheckboxChange={(newBody) => { onCheckboxChange(newBody) }}/>
bodyComponent = <NoteBodyViewer onJoplinLinkClick={this.onJoplinLinkClick_} style={this.styles().noteBodyViewer} webViewStyle={theme} note={note} onCheckboxChange={(newBody) => { onCheckboxChange(newBody) }}/>
} else {
const focusBody = !isNew && !!note.title;

View File

@@ -6,6 +6,7 @@ const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const { _ } = require('lib/locale.js');
const moment = require('moment');
const { markdownUtils } = require('lib/markdown-utils.js');
class BaseItem extends BaseModel {
@@ -649,6 +650,15 @@ class BaseItem extends BaseModel {
return super.save(o, options);
}
static markdownTag(item) {
const output = [];
output.push('[');
output.push(markdownUtils.escapeLinkText(item.title));
output.push(']');
output.push('(:/' + item.id + ')');
return output.join('');
}
}
BaseItem.encryptionService_ = null;