mirror of
https://github.com/heyman/heynote.git
synced 2024-11-22 16:03:28 +01:00
Replace block separators with "\n\n" when copying text.
Add Settings dialog. Started implementing Emacs-like keymap.
This commit is contained in:
parent
6c1e89c5b0
commit
924fd4b226
21
electron/config.js
Normal file
21
electron/config.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Store from "electron-store"
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
additionalProperties: false,
|
||||||
|
|
||||||
|
windowConfig: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
width: {type: "number"},
|
||||||
|
height: {type: "number"},
|
||||||
|
x: {type: "number"},
|
||||||
|
y: {type: "number"},
|
||||||
|
isMaximized: {type: "boolean"},
|
||||||
|
isFullScreen: {type: "boolean"},
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
keymap: { "enum": ["default", "emacs"] },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Store({schema})
|
@ -1 +1,3 @@
|
|||||||
export const WINDOW_CLOSE_EVENT = "window-close"
|
export const WINDOW_CLOSE_EVENT = "window-close"
|
||||||
|
export const KEYMAP_CHANGE_EVENT = "keymap-change"
|
||||||
|
export const OPEN_SETTINGS_EVENT = "open-settings"
|
||||||
|
27
electron/keymap.js
Normal file
27
electron/keymap.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { isMac } from "./detect-platform"
|
||||||
|
|
||||||
|
|
||||||
|
export function onBeforeInputEvent({win, event, input, currentKeymap}) {
|
||||||
|
//console.log("keyboard event", input)
|
||||||
|
let metaKey = "alt"
|
||||||
|
if (isMac) {
|
||||||
|
metaKey = "meta"
|
||||||
|
}
|
||||||
|
if (currentKeymap === "emacs") {
|
||||||
|
/**
|
||||||
|
* When using Emacs keymap, we can't bind shortcuts for copy, cut and paste in the the renderer process
|
||||||
|
* using Codemirror's bind function. Therefore we have to bind them in electron land, and send
|
||||||
|
* cut, paste and copy to window.webContents
|
||||||
|
*/
|
||||||
|
if (input.key === "y" && input.control) {
|
||||||
|
event.preventDefault()
|
||||||
|
win.webContents.paste()
|
||||||
|
} else if (input.key === "w" && input.control) {
|
||||||
|
event.preventDefault()
|
||||||
|
win.webContents.cut()
|
||||||
|
} else if (input.key === "w" && input[metaKey]) {
|
||||||
|
event.preventDefault()
|
||||||
|
win.webContents.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme } from 'electron'
|
import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme, globalShortcut } from 'electron'
|
||||||
import { release } from 'node:os'
|
import { release } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import * as jetpack from "fs-jetpack";
|
import * as jetpack from "fs-jetpack";
|
||||||
import Store from "electron-store"
|
|
||||||
import menu from './menu'
|
import menu from './menu'
|
||||||
import { initialContent, initialDevContent } from '../initial-content'
|
import { initialContent, initialDevContent } from '../initial-content'
|
||||||
import { WINDOW_CLOSE_EVENT } from '../constants';
|
import { WINDOW_CLOSE_EVENT, KEYMAP_CHANGE_EVENT } from '../constants';
|
||||||
|
import CONFIG from "../config"
|
||||||
|
import { onBeforeInputEvent } from "../keymap"
|
||||||
|
import { isMac } from '../detect-platform';
|
||||||
|
|
||||||
// The built directory structure
|
// The built directory structure
|
||||||
//
|
//
|
||||||
@ -37,8 +39,6 @@ if (!process.env.VITE_DEV_SERVER_URL && !app.requestSingleInstanceLock()) {
|
|||||||
// Set custom application menu
|
// Set custom application menu
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
|
|
||||||
const CONFIG = new Store()
|
|
||||||
|
|
||||||
|
|
||||||
// Remove electron security warnings
|
// Remove electron security warnings
|
||||||
// This warning only shows in development mode
|
// This warning only shows in development mode
|
||||||
@ -51,6 +51,8 @@ const preload = join(__dirname, '../preload/index.js')
|
|||||||
const url = process.env.VITE_DEV_SERVER_URL
|
const url = process.env.VITE_DEV_SERVER_URL
|
||||||
const indexHtml = join(process.env.DIST, 'index.html')
|
const indexHtml = join(process.env.DIST, 'index.html')
|
||||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||||
|
|
||||||
|
let currentKeymap = CONFIG.get("keymap", "default")
|
||||||
let contentSaved = false
|
let contentSaved = false
|
||||||
|
|
||||||
|
|
||||||
@ -115,6 +117,11 @@ async function createWindow() {
|
|||||||
//win.webContents.openDevTools()
|
//win.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// custom keyboard shortcuts for Emacs keybindings
|
||||||
|
win.webContents.on("before-input-event", function (event, input) {
|
||||||
|
onBeforeInputEvent({event, input, win, currentKeymap})
|
||||||
|
})
|
||||||
|
|
||||||
// Test actively push message to the Electron-Renderer
|
// Test actively push message to the Electron-Renderer
|
||||||
win.webContents.on('did-finish-load', () => {
|
win.webContents.on('did-finish-load', () => {
|
||||||
win?.webContents.send('main-process-message', new Date().toLocaleString())
|
win?.webContents.send('main-process-message', new Date().toLocaleString())
|
||||||
@ -183,3 +190,9 @@ ipcMain.handle('buffer-content:saveAndQuit', async (event, content) => {
|
|||||||
contentSaved = true
|
contentSaved = true
|
||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('keymap:set', (event, keymap) => {
|
||||||
|
currentKeymap = keymap
|
||||||
|
win?.webContents.send(KEYMAP_CHANGE_EVENT, keymap)
|
||||||
|
CONFIG.set("keymap", keymap)
|
||||||
|
})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { app, Menu } = require('electron')
|
const { app, Menu } = require('electron')
|
||||||
|
import { WINDOW_CLOSE_EVENT, KEYMAP_CHANGE_EVENT, OPEN_SETTINGS_EVENT } from '../constants';
|
||||||
|
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
||||||
@ -9,6 +10,14 @@ const template = [
|
|||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'about' },
|
{ role: 'about' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Preferences',
|
||||||
|
click: (menuItem, window, event) => {
|
||||||
|
window?.webContents.send(OPEN_SETTINGS_EVENT)
|
||||||
|
},
|
||||||
|
accelerator: isMac ? 'Command+,': null,
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{ role: 'services' },
|
{ role: 'services' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'hide' },
|
{ role: 'hide' },
|
||||||
|
@ -2,7 +2,8 @@ const { contextBridge } = require('electron')
|
|||||||
import darkMode from "./theme-mode"
|
import darkMode from "./theme-mode"
|
||||||
import { isMac, isWindows, isLinux } from "../detect-platform"
|
import { isMac, isWindows, isLinux } from "../detect-platform"
|
||||||
import { ipcRenderer } from "electron"
|
import { ipcRenderer } from "electron"
|
||||||
import { WINDOW_CLOSE_EVENT } from "../constants"
|
import { WINDOW_CLOSE_EVENT, KEYMAP_CHANGE_EVENT, OPEN_SETTINGS_EVENT } from "../constants"
|
||||||
|
import CONFIG from "../config"
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("platform", {
|
contextBridge.exposeInMainWorld("platform", {
|
||||||
isMac,
|
isMac,
|
||||||
@ -21,6 +22,10 @@ contextBridge.exposeInMainWorld("heynote", {
|
|||||||
ipcRenderer.on(WINDOW_CLOSE_EVENT, callback)
|
ipcRenderer.on(WINDOW_CLOSE_EVENT, callback)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onOpenSettings(callback) {
|
||||||
|
ipcRenderer.on(OPEN_SETTINGS_EVENT, callback)
|
||||||
|
},
|
||||||
|
|
||||||
buffer: {
|
buffer: {
|
||||||
async load() {
|
async load() {
|
||||||
return await ipcRenderer.invoke("buffer-content:load")
|
return await ipcRenderer.invoke("buffer-content:load")
|
||||||
@ -33,7 +38,17 @@ contextBridge.exposeInMainWorld("heynote", {
|
|||||||
async saveAndQuit(content) {
|
async saveAndQuit(content) {
|
||||||
return await ipcRenderer.invoke("buffer-content:saveAndQuit", content)
|
return await ipcRenderer.invoke("buffer-content:saveAndQuit", content)
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
|
keymap: {
|
||||||
|
set(keymap) {
|
||||||
|
ipcRenderer.invoke("keymap:set", keymap);
|
||||||
|
},
|
||||||
|
initial: CONFIG.get("keymap", "default"),
|
||||||
|
onKeymapChange(callback) {
|
||||||
|
ipcRenderer.on(KEYMAP_CHANGE_EVENT, (event, keymap) => callback(keymap))
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
import StatusBar from './StatusBar.vue'
|
import StatusBar from './StatusBar.vue'
|
||||||
import Editor from './Editor.vue'
|
import Editor from './Editor.vue'
|
||||||
import LanguageSelector from './LanguageSelector.vue'
|
import LanguageSelector from './LanguageSelector.vue'
|
||||||
|
import Settings from './settings/Settings.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Editor,
|
Editor,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
|
Settings,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -21,6 +23,8 @@
|
|||||||
systemTheme: 'system',
|
systemTheme: 'system',
|
||||||
development: window.location.href.indexOf("dev=1") !== -1,
|
development: window.location.href.indexOf("dev=1") !== -1,
|
||||||
showLanguageSelector: false,
|
showLanguageSelector: false,
|
||||||
|
showSettings: false,
|
||||||
|
keymap: window.heynote.keymap.initial,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -32,6 +36,12 @@
|
|||||||
window.darkMode.onChange((theme) => {
|
window.darkMode.onChange((theme) => {
|
||||||
this.theme = theme
|
this.theme = theme
|
||||||
})
|
})
|
||||||
|
window.heynote.keymap.onKeymapChange((keymap) => {
|
||||||
|
this.keymap = keymap
|
||||||
|
})
|
||||||
|
window.heynote.onOpenSettings(() => {
|
||||||
|
this.showSettings = true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
@ -39,6 +49,14 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
openSettings() {
|
||||||
|
this.showSettings = true
|
||||||
|
},
|
||||||
|
closeSettings() {
|
||||||
|
this.showSettings = false
|
||||||
|
this.$refs.editor.focus()
|
||||||
|
},
|
||||||
|
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
let newTheme
|
let newTheme
|
||||||
// when the "system" theme is used, make sure that the first click always results in amn actual theme change
|
// when the "system" theme is used, make sure that the first click always results in amn actual theme change
|
||||||
@ -85,6 +103,7 @@
|
|||||||
:theme="theme"
|
:theme="theme"
|
||||||
:development="development"
|
:development="development"
|
||||||
:debugSyntaxTree="false"
|
:debugSyntaxTree="false"
|
||||||
|
:keymap="keymap"
|
||||||
class="editor"
|
class="editor"
|
||||||
ref="editor"
|
ref="editor"
|
||||||
@openLanguageSelector="openLanguageSelector"
|
@openLanguageSelector="openLanguageSelector"
|
||||||
@ -106,6 +125,11 @@
|
|||||||
@selectLanguage="onSelectLanguage"
|
@selectLanguage="onSelectLanguage"
|
||||||
@close="closeLanguageSelector"
|
@close="closeLanguageSelector"
|
||||||
/>
|
/>
|
||||||
|
<Settings
|
||||||
|
v-if="showSettings"
|
||||||
|
:keymap="keymap"
|
||||||
|
@closeSettings="closeSettings"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
import { syntaxTree } from "@codemirror/language"
|
import { syntaxTree } from "@codemirror/language"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: [
|
props: {
|
||||||
"theme",
|
theme: String,
|
||||||
"development",
|
development: Boolean,
|
||||||
"debugSyntaxTree",
|
debugSyntaxTree: Boolean,
|
||||||
],
|
keymap: {
|
||||||
|
type: String,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -38,6 +42,7 @@
|
|||||||
saveFunction: (content) => {
|
saveFunction: (content) => {
|
||||||
window.heynote.buffer.save(content)
|
window.heynote.buffer.save(content)
|
||||||
},
|
},
|
||||||
|
keymap: this.keymap,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// set up window close handler that will save the buffer and quit
|
// set up window close handler that will save the buffer and quit
|
||||||
@ -69,6 +74,10 @@
|
|||||||
theme(newTheme) {
|
theme(newTheme) {
|
||||||
this.editor.setTheme(newTheme)
|
this.editor.setTheme(newTheme)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
keymap(keymap) {
|
||||||
|
this.editor.setKeymap(keymap)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
113
src/components/settings/Settings.vue
Normal file
113
src/components/settings/Settings.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
keymap: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("keydown", this.onKeyDown);
|
||||||
|
this.$refs.keymapSelector.focus()
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeymapChange(event) {
|
||||||
|
window.heynote.keymap.set(event.target.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
this.$emit("closeSettings")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings">
|
||||||
|
<div class="dialog">
|
||||||
|
<div>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<div class="entry">
|
||||||
|
<h2>Keymap:</h2>
|
||||||
|
<select ref="keymapSelector" @change="onKeymapChange">
|
||||||
|
<option :selected="keymap==='default'" value="default">Default</option>
|
||||||
|
<option :selected="keymap==='emacs'" value="emacs">Emacs</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="$emit('closeSettings')"
|
||||||
|
class="close"
|
||||||
|
>Close</button>
|
||||||
|
</div>
|
||||||
|
<div class="shader"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
=dark-mode()
|
||||||
|
@media (prefers-color-scheme: dark)
|
||||||
|
@content
|
||||||
|
|
||||||
|
.settings
|
||||||
|
position: fixed
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
|
||||||
|
.shader
|
||||||
|
z-index: 1
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
background: rgba(0, 0, 0, 0.5)
|
||||||
|
|
||||||
|
.dialog
|
||||||
|
box-sizing: border-box
|
||||||
|
z-index: 2
|
||||||
|
position: absolute
|
||||||
|
left: 50%
|
||||||
|
top: 50%
|
||||||
|
transform: translate(-50%, -50%)
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
max-width: 700px
|
||||||
|
max-height: 500px
|
||||||
|
border-radius: 5px
|
||||||
|
padding: 40px
|
||||||
|
background: #fff
|
||||||
|
color: #333
|
||||||
|
&:active, &:selected, &:focus, &:focus-visible
|
||||||
|
border: none
|
||||||
|
outline: none
|
||||||
|
+dark-mode
|
||||||
|
background: #333
|
||||||
|
color: #eee
|
||||||
|
h1
|
||||||
|
font-size: 20px
|
||||||
|
font-weight: 600
|
||||||
|
margin-bottom: 20px
|
||||||
|
|
||||||
|
.entry
|
||||||
|
margin-bottom: 20px
|
||||||
|
h2
|
||||||
|
font-weight: 600
|
||||||
|
margin-bottom: 10px
|
||||||
|
select
|
||||||
|
width: 200px
|
||||||
|
&:focus
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
.close
|
||||||
|
height: 32px
|
||||||
|
position: absolute
|
||||||
|
bottom: 30px
|
||||||
|
right: 30px
|
||||||
|
</style>
|
57
src/editor/copy-paste.js
Normal file
57
src/editor/copy-paste.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { EditorState, EditorSelection } from "@codemirror/state"
|
||||||
|
import { EditorView } from "@codemirror/view"
|
||||||
|
|
||||||
|
import { LANGUAGES } from './languages.js';
|
||||||
|
|
||||||
|
|
||||||
|
const languageTokensMatcher = LANGUAGES.map(l => l.token).join("|")
|
||||||
|
const blockSeparatorRegex = new RegExp(`\\n∞∞∞(${languageTokensMatcher})(-a)?\\n`, "g")
|
||||||
|
|
||||||
|
|
||||||
|
function copiedRange(state) {
|
||||||
|
let content = [], ranges = []
|
||||||
|
for (let range of state.selection.ranges) if (!range.empty) {
|
||||||
|
content.push(state.sliceDoc(range.from, range.to))
|
||||||
|
ranges.push(range)
|
||||||
|
}
|
||||||
|
return { text: content.join(state.lineBreak), ranges }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const heynoteCopyPaste = (editor) => {
|
||||||
|
let copy, cut
|
||||||
|
copy = cut = (event, view) => {
|
||||||
|
let { text, ranges } = copiedRange(view.state)
|
||||||
|
text = text.replaceAll(blockSeparatorRegex, "\n\n")
|
||||||
|
let data = event.clipboardData
|
||||||
|
if (data) {
|
||||||
|
event.preventDefault()
|
||||||
|
data.clearData()
|
||||||
|
data.setData("text/plain", text)
|
||||||
|
}
|
||||||
|
if (event.type == "cut" && !view.state.readOnly) {
|
||||||
|
view.dispatch({
|
||||||
|
changes: ranges,
|
||||||
|
scrollIntoView: true,
|
||||||
|
userEvent: "delete.cut"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if Editor.deselectOnCopy is set (e.g. we're in Emacs mode), we want to remove the selection after we've copied the text
|
||||||
|
if (editor.deselectOnCopy && event.type == "copy") {
|
||||||
|
const newSelection = EditorSelection.create(
|
||||||
|
view.state.selection.ranges.map(r => EditorSelection.cursor(r.head)),
|
||||||
|
view.state.selection.mainIndex,
|
||||||
|
)
|
||||||
|
view.dispatch(view.state.update({
|
||||||
|
selection: newSelection,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditorView.domEventHandlers({
|
||||||
|
copy,
|
||||||
|
cut,
|
||||||
|
})
|
||||||
|
}
|
@ -9,27 +9,39 @@ import { customSetup } from "./setup.js"
|
|||||||
import { heynoteLang } from "./lang-heynote/heynote.js"
|
import { heynoteLang } from "./lang-heynote/heynote.js"
|
||||||
import { noteBlockExtension } from "./block/block.js"
|
import { noteBlockExtension } from "./block/block.js"
|
||||||
import { changeCurrentBlockLanguage } from "./block/commands.js"
|
import { changeCurrentBlockLanguage } from "./block/commands.js"
|
||||||
import { heynoteKeymap } from "./keymap.js"
|
import { heynoteKeymap, emacsKeymap } from "./keymap.js"
|
||||||
|
import { heynoteCopyPaste } from "./copy-paste"
|
||||||
import { languageDetection } from "./language-detection/autodetect.js"
|
import { languageDetection } from "./language-detection/autodetect.js"
|
||||||
import { autoSaveContent } from "./save.js"
|
import { autoSaveContent } from "./save.js"
|
||||||
|
|
||||||
export const LANGUAGE_SELECTOR_EVENT = "openLanguageSelector"
|
export const LANGUAGE_SELECTOR_EVENT = "openLanguageSelector"
|
||||||
|
|
||||||
|
function getKeymapExtensions(editor, keymap) {
|
||||||
|
if (keymap === "emacs") {
|
||||||
|
return emacsKeymap(editor)
|
||||||
|
} else {
|
||||||
|
return heynoteKeymap(editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class HeynoteEditor {
|
export class HeynoteEditor {
|
||||||
constructor({element, content, focus=true, theme="light", saveFunction=null}) {
|
constructor({element, content, focus=true, theme="light", saveFunction=null, keymap="default"}) {
|
||||||
this.element = element
|
this.element = element
|
||||||
this.theme = new Compartment
|
this.themeCompartment = new Compartment
|
||||||
|
this.keymapCompartment = new Compartment
|
||||||
|
this.deselectOnCopy = keymap === "emacs"
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: content || "",
|
doc: content || "",
|
||||||
extensions: [
|
extensions: [
|
||||||
heynoteKeymap(this),
|
this.keymapCompartment.of(getKeymapExtensions(this, keymap)),
|
||||||
|
heynoteCopyPaste(this),
|
||||||
|
|
||||||
//minimalSetup,
|
//minimalSetup,
|
||||||
customSetup,
|
customSetup,
|
||||||
|
|
||||||
this.theme.of(theme === "dark" ? heynoteDark : heynoteLight),
|
this.themeCompartment.of(theme === "dark" ? heynoteDark : heynoteLight),
|
||||||
heynoteBase,
|
heynoteBase,
|
||||||
indentUnit.of(" "),
|
indentUnit.of(" "),
|
||||||
EditorView.scrollMargins.of(f => {
|
EditorView.scrollMargins.of(f => {
|
||||||
@ -71,7 +83,14 @@ export class HeynoteEditor {
|
|||||||
|
|
||||||
setTheme(theme) {
|
setTheme(theme) {
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
effects: this.theme.reconfigure(theme === "dark" ? heynoteDark : heynoteLight),
|
effects: this.themeCompartment.reconfigure(theme === "dark" ? heynoteDark : heynoteLight),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeymap(keymap) {
|
||||||
|
this.deselectOnCopy = keymap === "emacs"
|
||||||
|
this.view.dispatch({
|
||||||
|
effects: this.keymapCompartment.reconfigure(getKeymapExtensions(this, keymap)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,54 +1,40 @@
|
|||||||
import { keymap } from "@codemirror/view"
|
import { EditorView, keymap } from "@codemirror/view"
|
||||||
import { indentWithTab, insertTab, indentLess, indentMore } from "@codemirror/commands"
|
import { EditorSelection } from "@codemirror/state"
|
||||||
|
import { indentWithTab, insertTab, indentLess, indentMore, undo, redo } from "@codemirror/commands"
|
||||||
import { insertNewBlockAtCursor, addNewBlockAfterCurrent, moveLineUp, selectAll, gotoPreviousBlock, gotoNextBlock } from "./block/commands.js";
|
import { insertNewBlockAtCursor, addNewBlockAfterCurrent, moveLineUp, selectAll, gotoPreviousBlock, gotoNextBlock } from "./block/commands.js";
|
||||||
|
|
||||||
export function heynoteKeymap(editor) {
|
export function heynoteKeymap(editor) {
|
||||||
return keymap.of([
|
return keymap.of([
|
||||||
{
|
["Tab", indentMore],
|
||||||
key: "Tab",
|
["Shift-Tab", indentLess],
|
||||||
|
["Mod-Enter", addNewBlockAfterCurrent],
|
||||||
|
["Mod-Shift-Enter", insertNewBlockAtCursor],
|
||||||
|
["Mod-a", selectAll],
|
||||||
|
["Alt-ArrowUp", moveLineUp],
|
||||||
|
["Mod-ArrowUp", gotoPreviousBlock],
|
||||||
|
["Mod-ArrowDown", gotoNextBlock],
|
||||||
|
["Mod-l", () => editor.openLanguageSelector()],
|
||||||
|
].map(([key, run]) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
run,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
//run: insertTab,
|
}
|
||||||
run: indentMore,
|
}))
|
||||||
},
|
}
|
||||||
{
|
|
||||||
key: 'Shift-Tab',
|
export function emacsKeymap(editor) {
|
||||||
preventDefault: true,
|
return [
|
||||||
run: indentLess,
|
heynoteKeymap(editor),
|
||||||
},
|
keymap.of([
|
||||||
{
|
["Ctrl-Shift--", undo],
|
||||||
key: "Mod-Enter",
|
["Ctrl-.", redo],
|
||||||
preventDefault: true,
|
].map(([key, run]) => {
|
||||||
run: addNewBlockAfterCurrent,
|
return {
|
||||||
},
|
key,
|
||||||
{
|
run,
|
||||||
key: "Mod-Shift-Enter",
|
preventDefault: true,
|
||||||
preventDefault: true,
|
}
|
||||||
run: insertNewBlockAtCursor,
|
})),
|
||||||
},
|
]
|
||||||
{
|
|
||||||
key: "Mod-a",
|
|
||||||
preventDefault: true,
|
|
||||||
run: selectAll,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Alt-ArrowUp",
|
|
||||||
preventDefault: true,
|
|
||||||
run: moveLineUp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Mod-ArrowUp",
|
|
||||||
preventDefault: true,
|
|
||||||
run: gotoPreviousBlock,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Mod-ArrowDown",
|
|
||||||
preventDefault: true,
|
|
||||||
run: gotoNextBlock,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Mod-l",
|
|
||||||
preventDefault: true,
|
|
||||||
run: () => editor.openLanguageSelector(),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user