diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index 7265b37c..993090eb 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, ADD_NEW_BLOCK } from "../annotation.js"; import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./block" import { moveLineDown, moveLineUp } from "./move-lines.js"; @@ -313,3 +313,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 1ea00786..d54ff836 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" import { closeBrackets } from "@codemirror/autocomplete"; @@ -9,11 +9,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" @@ -23,15 +23,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, @@ -47,6 +38,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 @@ -57,8 +49,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] : []), @@ -116,6 +110,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 05fc7a5c..089ccf22 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -14,10 +14,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) { @@ -57,3 +59,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