Skip to content

Commit

Permalink
Internal links: use a Monaco link provider (#2376)
Browse files Browse the repository at this point in the history
* use a link provider to linkify internal links

* document args and limit regex to valid noteID characters

* figured out how to skip the warning

* remove types, stop overloading iLink with an additional attribute

* more than zero

* Dennis is awesome

* handle bad note ID

* Update note-content-editor.tsx

clarify a comment

* comment workshopping

* preview component should also check for unopenable note

* clean up conditional

* types
  • Loading branch information
codebykat authored Oct 2, 2020
1 parent aebfc22 commit e805fd6
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 1 deletion.
10 changes: 9 additions & 1 deletion lib/components/note-preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type StateProps = {
isFocused: boolean;
note: T.Note | null;
noteId: T.EntityId | null;
notes: Map<T.EntityId, T.Note>;
searchQuery: string;
showRenderedView: boolean;
};
Expand All @@ -37,6 +38,7 @@ export const NotePreview: FunctionComponent<Props> = ({
isFocused,
note,
noteId,
notes,
openNote,
searchQuery,
showRenderedView,
Expand Down Expand Up @@ -89,7 +91,12 @@ export const NotePreview: FunctionComponent<Props> = ({
}

const [fullMatch, linkedNoteId] = match;
openNote(linkedNoteId as T.EntityId);
// if we try to open a note that doesn't exist in local state,
// then we annoyingly close the open note without opening anything else
// implicit else: links that aren't openable will just do nothing
if (notes.has(linkedNoteId as T.EntityId)) {
openNote(linkedNoteId as T.EntityId);
}
return;
}

Expand Down Expand Up @@ -170,6 +177,7 @@ const mapStateToProps: S.MapState<StateProps, OwnProps> = (state, props) => {
isFocused: state.ui.dialogs.length === 0 && !state.ui.showNoteInfo,
note,
noteId,
notes: state.data.notes,
searchQuery: state.ui.searchQuery,
showRenderedView:
!!note?.systemTags.includes('markdown') && !state.ui.editMode,
Expand Down
39 changes: 39 additions & 0 deletions lib/note-content-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type StateProps = {
lineLength: T.LineLength;
noteId: T.EntityId;
note: T.Note;
notes: Map<T.EntityId, T.Note>;
searchQuery: string;
spellCheckEnabled: boolean;
theme: T.Theme;
Expand All @@ -72,6 +73,7 @@ type DispatchProps = {
clearSearch: () => any;
editNote: (noteId: T.EntityId, changes: Partial<T.Note>) => any;
insertTask: () => any;
openNote: (noteId: T.EntityId) => any;
storeEditorSelection: (
noteId: T.EntityId,
start: number,
Expand Down Expand Up @@ -430,6 +432,41 @@ class NoteContentEditor extends Component<Props> {
this.editor = editor;
this.monaco = monaco;

monaco.languages.registerLinkProvider('plaintext', {
provideLinks: (model) => {
const matches = model.findMatches(
'simplenote://note/[a-zA-Z0-9-]+',
true, // searchOnlyEditableRange
true, // isRegex
false, // matchCase
null, // wordSeparators
false // captureMatches
);
return {
// don't set a URL on these links, because then Monaco skips resolveLink
// @cite: https://github.com/Microsoft/vscode/blob/8f89095aa6097f6e0014f2d459ef37820983ae55/src/vs/editor/contrib/links/getLinks.ts#L43:L65
links: matches.map(({ range }) => ({ range })),
};
},
resolveLink: (link) => {
const href = editor.getModel()?.getValueInRange(link.range) ?? '';
const match = /^simplenote:\/\/note\/(.+)$/.exec(href);
if (!match) {
return;
}

const [fullMatch, linkedNoteId] = match as [string, T.EntityId];

// if we try to open a note that doesn't exist in local state,
// then we annoyingly close the open note without opening anything else
// implicit else: links that aren't openable will just do nothing
if (this.props.notes.has(linkedNoteId)) {
this.props.openNote(linkedNoteId);
}
return { ...link, url: '#' }; // tell Monaco to do nothing and not complain about it
},
});

// remove keybindings; see https://github.com/microsoft/monaco-editor/issues/287
const shortcutsToDisable = [
'cancelSelection', // escape; we need to allow this to bubble up to clear search
Expand Down Expand Up @@ -975,6 +1012,7 @@ const mapStateToProps: S.MapState<StateProps> = (state) => ({
lineLength: state.settings.lineLength,
noteId: state.ui.openedNote,
note: state.data.notes.get(state.ui.openedNote),
notes: state.data.notes,
searchQuery: state.ui.searchQuery,
spellCheckEnabled: state.settings.spellCheckEnabled,
theme: selectors.getTheme(state),
Expand All @@ -984,6 +1022,7 @@ const mapDispatchToProps: S.MapDispatch<DispatchProps> = {
clearSearch: () => dispatch(search('')),
editNote: actions.data.editNote,
insertTask: () => ({ type: 'INSERT_TASK' }),
openNote: actions.ui.selectNote,
storeEditorSelection: (noteId, start, end, direction) => ({
type: 'STORE_EDITOR_SELECTION',
noteId,
Expand Down

0 comments on commit e805fd6

Please sign in to comment.