From 8478511898c47c142e20b1afdc516fe6536fd838 Mon Sep 17 00:00:00 2001 From: Fernando Pinedo Date: Thu, 4 Jan 2024 17:30:51 -0300 Subject: [PATCH] CHORE: added custom keymaps for markdown & Language specific keymaps --- src/editor/block/commands.js | 69 +++++++++++++++++++++++++++++++++++- src/editor/editor.js | 29 ++++++++------- src/editor/keymap.js | 35 +++++++++++++++++- 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index c52d5fa0..94489808 100644 --- a/src/editor/block/commands.js +++ b/src/editor/block/commands.js @@ -1,4 +1,4 @@ -import { EditorSelection } from "@codemirror/state" +import { EditorSelection, ChangeSet } from "@codemirror/state" import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED } from "../annotation.js"; import { blockState, getActiveNoteBlock, getNoteBlockFromPos } from "./block" import { moveLineDown, moveLineUp } from "./move-lines.js"; @@ -250,3 +250,70 @@ export function triggerCurrenciesLoaded(state, dispatch) { annotations: [heynoteEvent.of(CURRENCIES_LOADED)], })) } + +export function formatBold({state, dispatch}){ + let boldSelectionFrom = state.selection.main.from; + let boldSelectionTo = state.selection.main.to; + + /** + * Assuring that, if multiple blocks are selected, only + * the active block will be affected by its keymaps + */ + if(state.selection.main.from < getActiveNoteBlock(state).content.from){ + boldSelectionFrom = getActiveNoteBlock(state).content.from; + } + if(state.selection.main.to > getActiveNoteBlock(state).content.to){ + boldSelectionTo = getActiveNoteBlock(state).content.to; + } + dispatch({ + changes: [ + ChangeSet.of([ + {from: boldSelectionFrom, insert: "**"}, + {from: boldSelectionTo, insert: "**"} + ], state.doc.length) + ], + selection: EditorSelection.cursor(state.selection.main.from == state.selection.main.to ? state.selection.main.from + 2 : state.selection.main.from) + }) + return true; +} + +export function formatItalic({state, dispatch}){ + let italicSelectionFrom = state.selection.main.from; + let italicSelectionTo = state.selection.main.to; + + /** + * Assuring that, if multiple blocks are selected, only + * the active block will be affected by its keymaps + */ + if(state.selection.main.from < getActiveNoteBlock(state).content.from){ + italicSelectionFrom = getActiveNoteBlock(state).content.from; + } + if(state.selection.main.to > getActiveNoteBlock(state).content.to){ + italicSelectionTo = getActiveNoteBlock(state).content.to; + } + dispatch({ + changes: [ + ChangeSet.of([ + {from: italicSelectionFrom, insert: "*"}, + {from: italicSelectionTo, insert: "*"} + ], state.doc.length) + ], + // If there is no text selected, we create a formated text area and move the cursor inside it. + selection: EditorSelection.cursor(state.selection.main.from == state.selection.main.to ? state.selection.main.from + 1 : state.selection.main.from) + }) + return true; +} + +export function formatTitle({state, dispatch}){ + let currentLine = state.doc.lineAt(state.selection.main.head); + + dispatch({ + changes: [ + ChangeSet.of([ + {from: currentLine.from, insert: "# "} + ], state.doc.length) + ], + selection: EditorSelection.cursor(state.selection.main.from == state.selection.main.to ? state.selection.main.from + 2 : currentLine.to) + }) + return true; +} \ No newline at end of file diff --git a/src/editor/editor.js b/src/editor/editor.js index d215f99b..8b9e8b06 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -1,5 +1,5 @@ import { Annotation, EditorState, Compartment } from "@codemirror/state" -import { EditorView, keymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view" +import { EditorView, keymap as kmp, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view" import { indentUnit, forceParsing, foldGutter } from "@codemirror/language" import { markdown } from "@codemirror/lang-markdown" @@ -8,11 +8,11 @@ import { heynoteDark } from "./theme/dark.js" import { heynoteBase } from "./theme/base.js" import { customSetup } from "./setup.js" import { heynoteLang } from "./lang-heynote/heynote.js" -import { noteBlockExtension, blockLineNumbers, blockState } from "./block/block.js" +import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock } from "./block/block.js" import { heynoteEvent, SET_CONTENT } from "./annotation.js"; import { changeCurrentBlockLanguage, triggerCurrenciesLoaded } from "./block/commands.js" import { formatBlockContent } from "./block/format-code.js" -import { heynoteKeymap } from "./keymap.js" +import { heynoteKeymap, languageKeymap } from "./keymap.js" import { emacsKeymap } from "./emacs.js" import { heynoteCopyPaste } from "./copy-paste" import { languageDetection } from "./language-detection/autodetect.js" @@ -22,15 +22,6 @@ import { links } from "./links.js" export const LANGUAGE_SELECTOR_EVENT = "openLanguageSelector" -function getKeymapExtensions(editor, keymap) { - if (keymap === "emacs") { - return emacsKeymap(editor) - } else { - return heynoteKeymap(editor) - } -} - - export class HeynoteEditor { constructor({ element, @@ -45,6 +36,7 @@ export class HeynoteEditor { this.element = element this.themeCompartment = new Compartment this.keymapCompartment = new Compartment + this.languageKeymapCompartment = new Compartment this.lineNumberCompartmentPre = new Compartment this.lineNumberCompartment = new Compartment this.foldGutterCompartment = new Compartment @@ -54,8 +46,10 @@ export class HeynoteEditor { const state = EditorState.create({ doc: content || "", extensions: [ - this.keymapCompartment.of(getKeymapExtensions(this, keymap)), + this.keymapCompartment.of(this.getKeymapExtensions(this, keymap)), + this.languageKeymapCompartment.of(kmp.of([])), heynoteCopyPaste(this), + languageKeymap(this.languageKeymapCompartment), //minimalSetup, this.lineNumberCompartment.of(showLineNumberGutter ? [lineNumbers(), blockLineNumbers] : []), @@ -111,6 +105,15 @@ export class HeynoteEditor { } } + // Changed to a class function to use with languageKeymaps + getKeymapExtensions(editor, keymap) { + if (keymap === "emacs") { + return emacsKeymap(editor) + } else { + return heynoteKeymap(editor) + } + } + getContent() { return this.view.state.sliceDoc() } diff --git a/src/editor/keymap.js b/src/editor/keymap.js index bf26410c..c07cce83 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -13,10 +13,12 @@ import { selectNextBlock, selectPreviousBlock, gotoPreviousParagraph, gotoNextParagraph, selectNextParagraph, selectPreviousParagraph, - newCursorBelow, newCursorAbove, + newCursorBelow, newCursorAbove, formatBold, formatItalic, formatTitle, } from "./block/commands.js" import { formatBlockContent } from "./block/format-code.js" +import { EditorState } from "@codemirror/state" +import { getActiveNoteBlock } from "./block/block.js" export function keymapFromSpec(specs) { @@ -53,3 +55,34 @@ export function heynoteKeymap(editor) { {key:"Ctrl-ArrowDown", run:gotoNextParagraph, shift:selectNextParagraph}, ]) } + +// Custom keymaps for markdown formatting +const markdownKeymaps = { + language: "markdown", + keymap: keymap.of([ + {key:"Ctrl-b", run:formatBold}, + {key:"Ctrl-i", run:formatItalic}, + {key:"Ctrl-t", run:formatTitle} + ]) +} + +export function languageKeymap(keymapComp){ + // First, we check each transaction to see if the block language changed in some way + return EditorState.transactionExtender.of((tr)=>{ + if(getActiveNoteBlock(tr.startState).language.name != getActiveNoteBlock(tr.state).language.name){ + let targetLanguageKeymap; + // If it did, we assign a new keymap set for the language, or none if not defined + switch(getActiveNoteBlock(tr.state).language.name){ + case "markdown": + targetLanguageKeymap = markdownKeymaps.keymap; + break; + default: + targetLanguageKeymap = []; + break; + } + return { + effects: keymapComp.reconfigure(targetLanguageKeymap) + } + } + }) +} \ No newline at end of file