mirror of
https://github.com/heyman/heynote.git
synced 2025-06-27 13:01:51 +02:00
Merge branch 'main' into feat-configure-tab-size
This commit is contained in:
commit
4d08748b41
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build/release Electron app
|
- name: Build/release Electron app
|
||||||
#continue-on-error: true
|
#continue-on-error: true
|
||||||
uses: samuelmeuli/action-electron-builder@v1
|
uses: johannesjo/action-electron-builder@v1
|
||||||
with:
|
with:
|
||||||
# Specify electron-builder config file
|
# Specify electron-builder config file
|
||||||
args: -c electron-builder.json5
|
args: -c electron-builder.json5
|
||||||
@ -56,7 +56,7 @@ jobs:
|
|||||||
# macOS notarization API key
|
# macOS notarization API key
|
||||||
APPLE_API_KEY: ~/private_keys/AuthKey.p8
|
APPLE_API_KEY: ~/private_keys/AuthKey.p8
|
||||||
APPLE_API_KEY_ID: ${{ secrets.apple_api_key_id }}
|
APPLE_API_KEY_ID: ${{ secrets.apple_api_key_id }}
|
||||||
APPLE_API_KEY_ISSUER: ${{ secrets.apple_api_key_issuer_id }}
|
APPLE_API_ISSUER: ${{ secrets.apple_api_key_issuer_id }}
|
||||||
|
|
||||||
#- name: Print notarization-error.log
|
#- name: Print notarization-error.log
|
||||||
# if: ${{ matrix.os == 'macos-latest' }}
|
# if: ${{ matrix.os == 'macos-latest' }}
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
## General Information
|
## General Information
|
||||||
|
|
||||||
- Website: [heynote.com](https://heynote.com)
|
- [Website](https://heynote.com)
|
||||||
- Documentation: [heynote.com](https://heynote.com/docs/)
|
- [Documentation](https://heynote.com/docs/)
|
||||||
- Changelog: [heynote.com](https://heynote.com/docs/changelog/)
|
- [Changelog](https://heynote.com/docs/changelog/)
|
||||||
|
|
||||||
Heynote is a dedicated scratchpad for developers. It functions as a large persistent text buffer where you can write down anything you like. Works great for that Slack message you don't want to accidentally send, a JSON response from an API you're working with, notes from a meeting, your daily to-do list, etc.
|
Heynote is a dedicated scratchpad for developers. It functions as a large persistent text buffer where you can write down anything you like. Works great for that Slack message you don't want to accidentally send, a JSON response from an API you're working with, notes from a meeting, your daily to-do list, etc.
|
||||||
|
|
||||||
|
80
assets/font/open-sans/open-sans.css
Normal file
80
assets/font/open-sans/open-sans.css
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/* BEGIN Light */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/Light/OpenSans-Light.woff2?v=1.1.0") format("woff2"), url("./fonts/Light/OpenSans-Light.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* END Light */
|
||||||
|
/* BEGIN Light Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/LightItalic/OpenSans-LightItalic.woff2?v=1.1.0") format("woff2"), url("./fonts/LightItalic/OpenSans-LightItalic.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* END Light Italic */
|
||||||
|
/* BEGIN Regular */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/Regular/OpenSans-Regular.woff2?v=1.1.0") format("woff2"), url("./fonts/Regular/OpenSans-Regular.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* END Regular */
|
||||||
|
/* BEGIN Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/Italic/OpenSans-Italic.woff2?v=1.1.0") format("woff2"), url("./fonts/Italic/OpenSans-Italic.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* END Italic */
|
||||||
|
/* BEGIN Semibold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/Semibold/OpenSans-Semibold.woff2?v=1.1.0") format("woff2"), url("./fonts/Semibold/OpenSans-Semibold.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* END Semibold */
|
||||||
|
/* BEGIN Semibold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/SemiboldItalic/OpenSans-SemiboldItalic.woff2?v=1.1.0") format("woff2"), url("./fonts/SemiboldItalic/OpenSans-SemiboldItalic.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* END Semibold Italic */
|
||||||
|
/* BEGIN Bold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/Bold/OpenSans-Bold.woff2?v=1.1.0") format("woff2"), url("./fonts/Bold/OpenSans-Bold.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* END Bold */
|
||||||
|
/* BEGIN Bold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/BoldItalic/OpenSans-BoldItalic.woff2?v=1.1.0") format("woff2"), url("./fonts/BoldItalic/OpenSans-BoldItalic.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* END Bold Italic */
|
||||||
|
/* BEGIN Extrabold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/ExtraBold/OpenSans-ExtraBold.woff2?v=1.1.0") format("woff2"), url("./fonts/ExtraBold/OpenSans-ExtraBold.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 800;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* END Extrabold */
|
||||||
|
/* BEGIN Extrabold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url("./fonts/ExtraBoldItalic/OpenSans-ExtraBoldItalic.woff2?v=1.1.0") format("woff2"), url("./fonts/ExtraBoldItalic/OpenSans-ExtraBoldItalic.woff?v=1.1.0") format("woff");
|
||||||
|
font-weight: 800;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* END Extrabold Italic */
|
1
assets/icons/drag-vertical-dark.svg
Normal file
1
assets/icons/drag-vertical-dark.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><circle cx="4.5" cy="2.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="4.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="6.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="8.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="10.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="12.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="2.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="4.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="6.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="8.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="10.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="12.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="2.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="4.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="6.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="8.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="10.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="12.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="2.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="4.5" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="6.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="8.499" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="10.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="12.498" fill="#e6e6e6" r=".6" class="fill-000000"></circle></svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
assets/icons/drag-vertical-light.svg
Normal file
1
assets/icons/drag-vertical-light.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><circle cx="4.5" cy="2.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="4.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="6.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="8.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="10.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="4.5" cy="12.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="2.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="4.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="6.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="8.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="10.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="6.5" cy="12.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="2.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="4.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="6.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="8.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="10.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="8.499" cy="12.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="2.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="4.5" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="6.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="8.499" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="10.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle><circle cx="10.499" cy="12.498" fill="#b0b0b0" r=".6" class="fill-000000"></circle></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -2,6 +2,22 @@
|
|||||||
|
|
||||||
Here are the most notable changes in each release. For a more detailed list of changes, see the [Github Releases page](https://github.com/heyman/heynote/releases).
|
Here are the most notable changes in each release. For a more detailed list of changes, see the [Github Releases page](https://github.com/heyman/heynote/releases).
|
||||||
|
|
||||||
|
## 2.2.0-beta.2
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Added support for custom key bindings. See [the documentation](https://heynote.com/docs/#user-content-custom-key-bindings) for more info.
|
||||||
|
- Added a "command palette" that can be accessed by pressing `Ctrl/Cmd+Shift+P`, or just typing `>` in the buffer selector. The command palette allows you to discover all available commands in the app, and to quickly execute them.
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- Upgraded to latest version of Electron, Vue, electron-builder and other dependencies.
|
||||||
|
|
||||||
|
## 2.1.4
|
||||||
|
|
||||||
|
- Fix issue with positioning and size of todo list checkboxes in Markdown blocks when using a non-default font size, or a non-monospaced font.
|
||||||
|
- Fix issue when pressing `Ctrl/Cmd+A` in a text input inside a modal dialog (e.g. the buffer selector). Previously the select all command would be sent to the editor.
|
||||||
|
|
||||||
## 2.1.3
|
## 2.1.3
|
||||||
|
|
||||||
- Fix escaping issue in buffer selector (properly this time, hopefully)
|
- Fix escaping issue in buffer selector (properly this time, hopefully)
|
||||||
|
@ -68,6 +68,12 @@ Alt + Shift + F Format block content (works for JSON, JavaScript, HTML, C
|
|||||||
Alt Show menu
|
Alt Show menu
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Custom Key Bindings
|
||||||
|
|
||||||
|
Heynote supports custom key bindings which you can configure in the settings. The key bindings are evaluated from top to bottom, so a binding that comes before another one will take precedence. Most commands will stop the event from propagating, but some commands only applies in certain contexts and might not stop the event from propagating to a later key binding.
|
||||||
|
|
||||||
|
To disable one of the built in key bindings, you can add a new key binding with the same key combination for the command "Do nothing". This will stop the event from propagating to the built in key binding.
|
||||||
|
|
||||||
## Download/Installation
|
## Download/Installation
|
||||||
|
|
||||||
Download the appropriate (Mac, Windows or Linux) version from [heynote.com](https://heynote.com). The Windows build is not signed, so you might see some scary warning (I can not justify paying a yearly fee for a certificate just to get rid of that).
|
Download the appropriate (Mac, Windows or Linux) version from [heynote.com](https://heynote.com). The Windows build is not signed, so you might see some scary warning (I can not justify paying a yearly fee for a certificate just to get rid of that).
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"dist-electron",
|
"dist-electron",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"afterSign": "electron-builder-notarize",
|
|
||||||
"mac": {
|
"mac": {
|
||||||
"artifactName": "${productName}_${version}_${arch}.${ext}",
|
"artifactName": "${productName}_${version}_${arch}.${ext}",
|
||||||
"target": [
|
"target": [
|
||||||
|
@ -24,6 +24,19 @@ const schema = {
|
|||||||
properties: {
|
properties: {
|
||||||
"keymap": { "enum": ["default", "emacs"], default:"default" },
|
"keymap": { "enum": ["default", "emacs"], default:"default" },
|
||||||
"emacsMetaKey": { "enum": [null, "alt", "meta"], default: null },
|
"emacsMetaKey": { "enum": [null, "alt", "meta"], default: null },
|
||||||
|
"keyBindings": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["key", "command"],
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" },
|
||||||
|
"command": { "type": "string" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"showLineNumberGutter": {type: "boolean", default:true},
|
"showLineNumberGutter": {type: "boolean", default:true},
|
||||||
"showFoldGutter": {type: "boolean", default:true},
|
"showFoldGutter": {type: "boolean", default:true},
|
||||||
"autoUpdate": {type: "boolean", default: true},
|
"autoUpdate": {type: "boolean", default: true},
|
||||||
@ -62,6 +75,7 @@ const defaults = {
|
|||||||
settings: {
|
settings: {
|
||||||
keymap: "default",
|
keymap: "default",
|
||||||
emacsMetaKey: isMac ? "meta" : "alt",
|
emacsMetaKey: isMac ? "meta" : "alt",
|
||||||
|
keyBindings: [],
|
||||||
showLineNumberGutter: true,
|
showLineNumberGutter: true,
|
||||||
showFoldGutter: true,
|
showFoldGutter: true,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
|
@ -64,8 +64,6 @@ 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')
|
||||||
|
|
||||||
let currentKeymap = CONFIG.get("settings.keymap")
|
|
||||||
|
|
||||||
// if this version is a beta version, set the release channel to beta
|
// if this version is a beta version, set the release channel to beta
|
||||||
const isBetaVersion = app.getVersion().includes("beta")
|
const isBetaVersion = app.getVersion().includes("beta")
|
||||||
if (isBetaVersion) {
|
if (isBetaVersion) {
|
||||||
@ -85,8 +83,8 @@ export function quit() {
|
|||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
// read any stored window settings from config, or use defaults
|
// read any stored window settings from config, or use defaults
|
||||||
let windowConfig = {
|
let windowConfig = {
|
||||||
width: CONFIG.get("windowConfig.width", 900) as number,
|
width: CONFIG.get("windowConfig.width", 940) as number,
|
||||||
height: CONFIG.get("windowConfig.height", 680) as number,
|
height: CONFIG.get("windowConfig.height", 720) as number,
|
||||||
isMaximized: CONFIG.get("windowConfig.isMaximized", false) as boolean,
|
isMaximized: CONFIG.get("windowConfig.isMaximized", false) as boolean,
|
||||||
isFullScreen: CONFIG.get("windowConfig.isFullScreen", false) as boolean,
|
isFullScreen: CONFIG.get("windowConfig.isFullScreen", false) as boolean,
|
||||||
x: CONFIG.get("windowConfig.x"),
|
x: CONFIG.get("windowConfig.x"),
|
||||||
@ -409,9 +407,6 @@ ipcMain.handle("getInitErrors", () => {
|
|||||||
|
|
||||||
|
|
||||||
ipcMain.handle('settings:set', async (event, settings) => {
|
ipcMain.handle('settings:set', async (event, settings) => {
|
||||||
if (settings.keymap !== CONFIG.get("settings.keymap")) {
|
|
||||||
currentKeymap = settings.keymap
|
|
||||||
}
|
|
||||||
let globalHotkeyChanged = settings.enableGlobalHotkey !== CONFIG.get("settings.enableGlobalHotkey") || settings.globalHotkey !== CONFIG.get("settings.globalHotkey")
|
let globalHotkeyChanged = settings.enableGlobalHotkey !== CONFIG.get("settings.enableGlobalHotkey") || settings.globalHotkey !== CONFIG.get("settings.globalHotkey")
|
||||||
let showInDockChanged = settings.showInDock !== CONFIG.get("settings.showInDock");
|
let showInDockChanged = settings.showInDock !== CONFIG.get("settings.showInDock");
|
||||||
let showInMenuChanged = settings.showInMenu !== CONFIG.get("settings.showInMenu");
|
let showInMenuChanged = settings.showInMenu !== CONFIG.get("settings.showInMenu");
|
||||||
|
8624
package-lock.json
generated
8624
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Heynote",
|
"name": "Heynote",
|
||||||
"version": "2.1.3",
|
"version": "2.2.0-beta.2",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"description": "A dedicated scratch pad",
|
"description": "A dedicated scratch pad",
|
||||||
"author": "Jonatan Heyman (https://heyman.info)",
|
"author": "Jonatan Heyman (https://heyman.info)",
|
||||||
@ -50,34 +50,35 @@
|
|||||||
"@codemirror/legacy-modes": "^6.3.3",
|
"@codemirror/legacy-modes": "^6.3.3",
|
||||||
"@codemirror/lint": "^6.4.2",
|
"@codemirror/lint": "^6.4.2",
|
||||||
"@codemirror/search": "^6.5.5",
|
"@codemirror/search": "^6.5.5",
|
||||||
"@codemirror/state": "^6.3.3",
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/view": "^6.22.2",
|
"@codemirror/view": "^6.36.5",
|
||||||
"@electron/asar": "^3.2.2",
|
"@electron/asar": "^3.2.2",
|
||||||
"@lezer/generator": "^1.5.1",
|
"@lezer/generator": "^1.5.1",
|
||||||
"@lezer/markdown": "^1.1.2",
|
"@lezer/markdown": "^1.1.2",
|
||||||
"@playwright/test": "^1.49.0",
|
"@playwright/test": "^1.51.1",
|
||||||
"@replit/codemirror-lang-csharp": "^6.2.0",
|
"@replit/codemirror-lang-csharp": "^6.2.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@types/node": "^20.10.5",
|
"@types/node": "^20.10.5",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"codemirror-lang-elixir": "^4.0.0",
|
"codemirror-lang-elixir": "^4.0.0",
|
||||||
"debounce": "^1.2.1",
|
"debounce": "^1.2.1",
|
||||||
"electron": "^33.3.1",
|
"electron": "^35.2.0",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-builder-notarize": "^1.5.1",
|
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.6.2",
|
||||||
"fs-jetpack": "^5.1.0",
|
"fs-jetpack": "^5.1.0",
|
||||||
"lezer-elixir": "^1.1.2",
|
"lezer-elixir": "^1.1.2",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
|
"primevue": "^4.3.3",
|
||||||
"rollup-plugin-license": "^3.0.1",
|
"rollup-plugin-license": "^3.0.1",
|
||||||
"sass": "^1.57.1",
|
"sass-embedded": "^1.87.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.5.2",
|
"vite": "^6.3.2",
|
||||||
"vite-plugin-electron": "^0.11.1",
|
"vite-plugin-electron": "^0.11.1",
|
||||||
"vite-plugin-electron-renderer": "^0.11.4",
|
"vite-plugin-electron-renderer": "^0.11.4",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.5.13",
|
||||||
"vue-tsc": "^1.0.16"
|
"vue-tsc": "^1.0.16",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
|
@ -20,7 +20,8 @@ export default defineConfig({
|
|||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
//workers: process.env.CI ? 1 : undefined,
|
||||||
|
workers: process.env.CI ? undefined : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: process.env.CI ? [['github'], ['html']] : 'list',
|
reporter: process.env.CI ? [['github'], ['html']] : 'list',
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
showCreateBuffer(value) { this.dialogWatcher(value) },
|
showCreateBuffer(value) { this.dialogWatcher(value) },
|
||||||
showEditBuffer(value) { this.dialogWatcher(value) },
|
showEditBuffer(value) { this.dialogWatcher(value) },
|
||||||
showMoveToBufferSelector(value) { this.dialogWatcher(value) },
|
showMoveToBufferSelector(value) { this.dialogWatcher(value) },
|
||||||
|
showCommandPalette(value) { this.dialogWatcher(value) },
|
||||||
|
|
||||||
currentBufferPath() {
|
currentBufferPath() {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
@ -85,11 +86,15 @@
|
|||||||
"showCreateBuffer",
|
"showCreateBuffer",
|
||||||
"showEditBuffer",
|
"showEditBuffer",
|
||||||
"showMoveToBufferSelector",
|
"showMoveToBufferSelector",
|
||||||
"openMoveToBufferSelector",
|
"showCommandPalette",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
dialogVisible() {
|
||||||
|
return this.showLanguageSelector || this.showBufferSelector || this.showCreateBuffer || this.showEditBuffer || this.showMoveToBufferSelector || this.showCommandPalette || this.showSettings
|
||||||
|
},
|
||||||
|
|
||||||
editorInert() {
|
editorInert() {
|
||||||
return this.showCreateBuffer || this.showSettings || this.showEditBuffer
|
return this.dialogVisible
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -173,7 +178,9 @@
|
|||||||
@close="closeDialog"
|
@close="closeDialog"
|
||||||
/>
|
/>
|
||||||
<BufferSelector
|
<BufferSelector
|
||||||
v-if="showBufferSelector"
|
v-if="showBufferSelector || showCommandPalette"
|
||||||
|
:initialFilter="showCommandPalette ? '>' : ''"
|
||||||
|
:commandsEnabled="true"
|
||||||
@openBuffer="openBuffer"
|
@openBuffer="openBuffer"
|
||||||
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('new', nameSuggestion)"
|
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('new', nameSuggestion)"
|
||||||
@close="closeBufferSelector"
|
@close="closeBufferSelector"
|
||||||
@ -181,6 +188,7 @@
|
|||||||
<BufferSelector
|
<BufferSelector
|
||||||
v-if="showMoveToBufferSelector"
|
v-if="showMoveToBufferSelector"
|
||||||
headline="Move block to..."
|
headline="Move block to..."
|
||||||
|
:commandsEnabled="false"
|
||||||
@openBuffer="onMoveCurrentBlockToOtherEditor"
|
@openBuffer="onMoveCurrentBlockToOtherEditor"
|
||||||
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('currentBlock', nameSuggestion)"
|
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('currentBlock', nameSuggestion)"
|
||||||
@close="closeMoveToBufferSelector"
|
@close="closeMoveToBufferSelector"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import { mapState, mapActions } from 'pinia'
|
import { mapState, mapActions } from 'pinia'
|
||||||
import { SCRATCH_FILE_NAME } from "../common/constants"
|
import { SCRATCH_FILE_NAME } from "../common/constants"
|
||||||
import { useHeynoteStore } from "../stores/heynote-store"
|
import { useHeynoteStore } from "../stores/heynote-store"
|
||||||
|
import { HEYNOTE_COMMANDS } from '../editor/commands'
|
||||||
|
|
||||||
const pathSep = window.heynote.buffer.pathSeparator
|
const pathSep = window.heynote.buffer.pathSeparator
|
||||||
|
|
||||||
@ -19,13 +20,15 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
headline: String,
|
headline: String,
|
||||||
|
initialFilter: String,
|
||||||
|
commandsEnabled: Boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selected: 0,
|
selected: 0,
|
||||||
actionButton: 0,
|
actionButton: 0,
|
||||||
filter: "",
|
filter: this.initialFilter || "",
|
||||||
items: [],
|
items: [],
|
||||||
SCRATCH_FILE_NAME: SCRATCH_FILE_NAME,
|
SCRATCH_FILE_NAME: SCRATCH_FILE_NAME,
|
||||||
deleteConfirm: false,
|
deleteConfirm: false,
|
||||||
@ -48,6 +51,25 @@
|
|||||||
"recentBufferPaths",
|
"recentBufferPaths",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
commands() {
|
||||||
|
const commands = Object.entries(HEYNOTE_COMMANDS)
|
||||||
|
// sort array first by category, then by description
|
||||||
|
commands.sort((a, b) => {
|
||||||
|
const aCategory = a[1].category || ""
|
||||||
|
const bCategory = b[1].category || ""
|
||||||
|
if (aCategory === bCategory) {
|
||||||
|
return a[1].description.localeCompare(b[1].description)
|
||||||
|
} else {
|
||||||
|
return aCategory.localeCompare(bCategory)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return commands.map(([cmdKey, cmd]) => ({
|
||||||
|
name: `${cmd.category}: ${cmd.description}`,
|
||||||
|
cmd: cmdKey,
|
||||||
|
isCommand: true,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
orderedItems() {
|
orderedItems() {
|
||||||
const sortKeys = Object.fromEntries(this.recentBufferPaths.map((item, idx) => [item, idx]))
|
const sortKeys = Object.fromEntries(this.recentBufferPaths.map((item, idx) => [item, idx]))
|
||||||
const getSortScore = (item) => sortKeys[item.path] !== undefined ? sortKeys[item.path] : 1000
|
const getSortScore = (item) => sortKeys[item.path] !== undefined ? sortKeys[item.path] : 1000
|
||||||
@ -74,10 +96,24 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
filteredItems() {
|
filteredItems() {
|
||||||
|
if (this.commandsEnabled && this.filter.startsWith(">")) {
|
||||||
|
// command mode if the first character is ">"
|
||||||
|
if (this.filter.length < 2) {
|
||||||
|
return this.commands
|
||||||
|
}
|
||||||
|
const searchResults = fuzzysort.go(this.filter.slice(1), this.commands, {
|
||||||
|
keys: ["name"],
|
||||||
|
})
|
||||||
|
return searchResults.map((result) => {
|
||||||
|
const obj = {...result.obj}
|
||||||
|
const nameHighlight = result[0].highlight("<b>", "</b>")
|
||||||
|
obj.name = nameHighlight !== "" ? nameHighlight : obj.name
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
} else {
|
||||||
let items
|
let items
|
||||||
if (this.filter === "") {
|
if (this.filter === "") {
|
||||||
items = this.orderedItems
|
items = this.orderedItems
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const searchResults = fuzzysort.go(this.filter, this.items, {
|
const searchResults = fuzzysort.go(this.filter, this.items, {
|
||||||
keys: ["name", "folder"],
|
keys: ["name", "folder"],
|
||||||
@ -100,6 +136,7 @@
|
|||||||
...items,
|
...items,
|
||||||
newNoteItem,
|
newNoteItem,
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -108,6 +145,7 @@
|
|||||||
"updateBuffers",
|
"updateBuffers",
|
||||||
"editBufferMetadata",
|
"editBufferMetadata",
|
||||||
"deleteBuffer",
|
"deleteBuffer",
|
||||||
|
"executeCommand",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
buildItems() {
|
buildItems() {
|
||||||
@ -187,13 +225,18 @@
|
|||||||
} else {
|
} else {
|
||||||
this.$emit("openCreateBuffer", "")
|
this.$emit("openCreateBuffer", "")
|
||||||
}
|
}
|
||||||
|
} else if (item.isCommand) {
|
||||||
|
this.$emit("close")
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.executeCommand(item.cmd)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$emit("openBuffer", item.path)
|
this.$emit("openBuffer", item.path)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
itemHasActionButtons(item) {
|
itemHasActionButtons(item) {
|
||||||
return !item.createNew && item.path !== SCRATCH_FILE_NAME
|
return !item.createNew && item.path !== SCRATCH_FILE_NAME && !item.isCommand
|
||||||
},
|
},
|
||||||
|
|
||||||
onInput(event) {
|
onInput(event) {
|
||||||
@ -248,7 +291,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container">
|
<form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container" @submit.prevent>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<h1 v-if="headline">{{headline}}</h1>
|
<h1 v-if="headline">{{headline}}</h1>
|
||||||
<input
|
<input
|
||||||
@ -308,12 +351,11 @@
|
|||||||
<style scoped lang="sass">
|
<style scoped lang="sass">
|
||||||
.note-selector
|
.note-selector
|
||||||
font-size: 13px
|
font-size: 13px
|
||||||
//background: #48b57e
|
|
||||||
background: #efefef
|
background: #efefef
|
||||||
position: absolute
|
position: absolute
|
||||||
top: 0
|
top: 0
|
||||||
left: 50%
|
left: 50%
|
||||||
width: 420px
|
width: 440px
|
||||||
transform: translateX(-50%)
|
transform: translateX(-50%)
|
||||||
max-height: 100%
|
max-height: 100%
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
|
@ -120,6 +120,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
onInputKeydown(event) {
|
onInputKeydown(event) {
|
||||||
|
// support Ctrl/Cmd+A to select all
|
||||||
|
if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.srcElement.select()
|
||||||
|
}
|
||||||
|
|
||||||
// redirect arrow keys and page up/down to folder selector
|
// redirect arrow keys and page up/down to folder selector
|
||||||
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
||||||
if (redirectKeys.includes(event.key)) {
|
if (redirectKeys.includes(event.key)) {
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
syntaxTreeDebugContent: null,
|
syntaxTreeDebugContent: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
onWindowClose: null,
|
onWindowClose: null,
|
||||||
|
onUndo: null,
|
||||||
|
onRedo: null,
|
||||||
|
onDeleteBlock: null,
|
||||||
|
onSelectAll: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -39,29 +43,40 @@
|
|||||||
}
|
}
|
||||||
window.heynote.mainProcess.on(WINDOW_CLOSE_EVENT, this.onWindowClose)
|
window.heynote.mainProcess.on(WINDOW_CLOSE_EVENT, this.onWindowClose)
|
||||||
|
|
||||||
window.heynote.mainProcess.on(UNDO_EVENT, () => {
|
this.onUndo = () => {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
toRaw(this.editor).undo()
|
toRaw(this.editor).undo()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
window.heynote.mainProcess.on(UNDO_EVENT, this.onUndo)
|
||||||
|
|
||||||
window.heynote.mainProcess.on(REDO_EVENT, () => {
|
this.onRedo = () => {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
toRaw(this.editor).redo()
|
toRaw(this.editor).redo()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
window.heynote.mainProcess.on(REDO_EVENT, this.onRedo)
|
||||||
|
|
||||||
window.heynote.mainProcess.on(DELETE_BLOCK_EVENT, () => {
|
this.onDeleteBlock = () => {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
toRaw(this.editor).deleteActiveBlock()
|
toRaw(this.editor).deleteActiveBlock()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
window.heynote.mainProcess.on(DELETE_BLOCK_EVENT, this.onDeleteBlock)
|
||||||
|
|
||||||
window.heynote.mainProcess.on(SELECT_ALL_EVENT, () => {
|
this.onSelectAll = () => {
|
||||||
if (this.editor) {
|
const activeEl = document.activeElement
|
||||||
|
if (activeEl && activeEl.tagName === "INPUT") {
|
||||||
|
// if the active element is an input, select all text in it
|
||||||
|
activeEl.select()
|
||||||
|
} else if (this.editor) {
|
||||||
|
// make sure the editor is focused
|
||||||
|
if (this.$refs.editor.contains(activeEl)) {
|
||||||
toRaw(this.editor).selectAll()
|
toRaw(this.editor).selectAll()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
window.heynote.mainProcess.on(SELECT_ALL_EVENT, this.onSelectAll)
|
||||||
|
|
||||||
// if debugSyntaxTree prop is set, display syntax tree for debugging
|
// if debugSyntaxTree prop is set, display syntax tree for debugging
|
||||||
if (this.debugSyntaxTree) {
|
if (this.debugSyntaxTree) {
|
||||||
@ -85,10 +100,10 @@
|
|||||||
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.heynote.mainProcess.off(WINDOW_CLOSE_EVENT, this.onWindowClose)
|
window.heynote.mainProcess.off(WINDOW_CLOSE_EVENT, this.onWindowClose)
|
||||||
window.heynote.mainProcess.off(UNDO_EVENT)
|
window.heynote.mainProcess.off(UNDO_EVENT, this.onUndo)
|
||||||
window.heynote.mainProcess.off(REDO_EVENT)
|
window.heynote.mainProcess.off(REDO_EVENT, this.onRedo)
|
||||||
window.heynote.mainProcess.off(DELETE_BLOCK_EVENT)
|
window.heynote.mainProcess.off(DELETE_BLOCK_EVENT, this.onDeleteBlock)
|
||||||
window.heynote.mainProcess.off(SELECT_ALL_EVENT)
|
window.heynote.mainProcess.off(SELECT_ALL_EVENT, this.onSelectAll)
|
||||||
this.editorCacheStore.tearDown();
|
this.editorCacheStore.tearDown();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@
|
|||||||
border: 1px solid #ccc
|
border: 1px solid #ccc
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
border-radius: 2px
|
border-radius: 2px
|
||||||
width: 400px
|
width: 300px
|
||||||
margin-bottom: 10px
|
margin-bottom: 10px
|
||||||
&:focus
|
&:focus
|
||||||
outline: none
|
outline: none
|
||||||
|
@ -128,6 +128,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
onInputKeydown(event) {
|
onInputKeydown(event) {
|
||||||
|
// support Ctrl/Cmd+A to select all
|
||||||
|
if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.srcElement.select()
|
||||||
|
}
|
||||||
|
|
||||||
// redirect arrow keys and page up/down to folder selector
|
// redirect arrow keys and page up/down to folder selector
|
||||||
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
||||||
if (redirectKeys.includes(event.key)) {
|
if (redirectKeys.includes(event.key)) {
|
||||||
|
211
src/components/settings/AddKeyBind.vue
Normal file
211
src/components/settings/AddKeyBind.vue
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<script>
|
||||||
|
import fuzzysort from 'fuzzysort'
|
||||||
|
import AutoComplete from 'primevue/autocomplete'
|
||||||
|
|
||||||
|
import { HEYNOTE_COMMANDS } from '@/src/editor/commands'
|
||||||
|
import RecordKeyInput from './RecordKeyInput.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddKeyBind",
|
||||||
|
components: {
|
||||||
|
AutoComplete,
|
||||||
|
RecordKeyInput,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
key: "",
|
||||||
|
command: "",
|
||||||
|
commandSuggestions: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
commands() {
|
||||||
|
|
||||||
|
return Object.entries(HEYNOTE_COMMANDS).map(([key, cmd]) => {
|
||||||
|
const description = cmd.category + ": " + cmd.description
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
category: cmd.category,
|
||||||
|
description: description,
|
||||||
|
key: cmd.description,
|
||||||
|
label: description,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("keydown", this.onKeyDown)
|
||||||
|
this.$refs.keys.$el.focus()
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener("keydown", this.onKeyDown)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.key === "Escape" && document.activeElement !== this.$refs.keys.$el) {
|
||||||
|
this.$emit("close")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCommandSearch(event) {
|
||||||
|
if (event.query === "") {
|
||||||
|
this.commandSuggestions = [...this.commands]
|
||||||
|
} else {
|
||||||
|
const searchResults = fuzzysort.go(event.query, this.commands, {
|
||||||
|
keys: ["description", "name"],
|
||||||
|
})
|
||||||
|
this.commandSuggestions = searchResults.map((result) => {
|
||||||
|
const obj = {...result.obj}
|
||||||
|
const nameHighlight = result[0].highlight("<b>", "</b>")
|
||||||
|
obj.label = nameHighlight !== "" ? nameHighlight : obj.description
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSave() {
|
||||||
|
if (this.key === "" || this.command === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit("save", {
|
||||||
|
key: this.key,
|
||||||
|
command: this.command.name,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
focusCommandSelector() {
|
||||||
|
this.$refs.autocomplete.$el.querySelector("input").focus()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container add-key-binding-dialog">
|
||||||
|
<div class="dialog">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<h3>Add key binding</h3>
|
||||||
|
<div class="form">
|
||||||
|
<div class="field">
|
||||||
|
<label>Key</label>
|
||||||
|
<RecordKeyInput
|
||||||
|
v-model="key"
|
||||||
|
@enter="focusCommandSelector"
|
||||||
|
@close="$emit('close')"
|
||||||
|
ref="keys"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Command</label>
|
||||||
|
<AutoComplete
|
||||||
|
dropdown
|
||||||
|
forceSelection
|
||||||
|
v-model="command"
|
||||||
|
:suggestions="commandSuggestions"
|
||||||
|
:autoOptionFocus="true"
|
||||||
|
optionLabel="key"
|
||||||
|
:delay="0"
|
||||||
|
@complete="onCommandSearch"
|
||||||
|
ref="autocomplete"
|
||||||
|
emptySearchMessage="No commands found"
|
||||||
|
class="command-autocomplete"
|
||||||
|
>
|
||||||
|
<template #option="slotProps">
|
||||||
|
<div class="command-option">
|
||||||
|
<span v-html="slotProps.option.label" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AutoComplete>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<button
|
||||||
|
@click="onSave"
|
||||||
|
class="save"
|
||||||
|
>Save</button>
|
||||||
|
<button
|
||||||
|
@click="$emit('close')"
|
||||||
|
class="cancel"
|
||||||
|
>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.container
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
background: rgba(255,255,255, 0.7)
|
||||||
|
+dark-mode
|
||||||
|
background: rgba(51,51,51, 0.7)
|
||||||
|
|
||||||
|
.dialog
|
||||||
|
width: 400px
|
||||||
|
position: absolute
|
||||||
|
top: 50%
|
||||||
|
left: 50%
|
||||||
|
transform: translate(-50%, -50%)
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
background: #fff
|
||||||
|
border-radius: 5px
|
||||||
|
box-shadow: 0 5px 30px rgba(0,0,0, 0.3)
|
||||||
|
border: 2px solid #c0c0c0
|
||||||
|
+dark-mode
|
||||||
|
background: #333
|
||||||
|
box-shadow: 0 5px 30px rgba(0,0,0, 0.7)
|
||||||
|
border: 2px solid #555
|
||||||
|
.dialog-content
|
||||||
|
flex-grow: 1
|
||||||
|
padding: 20px
|
||||||
|
h3
|
||||||
|
font-size: 14px
|
||||||
|
font-weight: 600
|
||||||
|
text-align: center
|
||||||
|
margin: 0
|
||||||
|
margin-bottom: 30px
|
||||||
|
.form
|
||||||
|
//display: flex
|
||||||
|
.field
|
||||||
|
//width: 50%
|
||||||
|
margin-bottom: 20px
|
||||||
|
&:last-child
|
||||||
|
margin-bottom: 0
|
||||||
|
label
|
||||||
|
display: block
|
||||||
|
margin-bottom: 8px
|
||||||
|
input.keys
|
||||||
|
width: 100%
|
||||||
|
padding: 4px
|
||||||
|
border-radius: 2px
|
||||||
|
border: 1px solid #ccc
|
||||||
|
&:focus
|
||||||
|
border: 1px solid rgba(0,0,0, 0)
|
||||||
|
outline: 2px solid var(--highlight-color)
|
||||||
|
//border: 1px solid var(--highlight-color)
|
||||||
|
+dark-mode
|
||||||
|
background: #202020
|
||||||
|
color: #fff
|
||||||
|
border: 1px solid #5a5a5a
|
||||||
|
&:focus
|
||||||
|
border: 1px solid rgba(0,0,0, 0)
|
||||||
|
.command-autocomplete
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.footer
|
||||||
|
padding: 10px
|
||||||
|
background: #f1f1f1
|
||||||
|
border-radius: 0 0 5px 5px
|
||||||
|
text-align: right
|
||||||
|
+dark-mode
|
||||||
|
background: #2c2c2c
|
||||||
|
.cancel
|
||||||
|
float: left
|
||||||
|
</style>
|
105
src/components/settings/KeyBindRow.vue
Normal file
105
src/components/settings/KeyBindRow.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<script>
|
||||||
|
import { HEYNOTE_COMMANDS } from '@/src/editor/commands'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
"keys",
|
||||||
|
"command",
|
||||||
|
"isDefault",
|
||||||
|
"source",
|
||||||
|
],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
formattedKeys() {
|
||||||
|
return this.keys.replaceAll(
|
||||||
|
"Mod",
|
||||||
|
window.heynote.platform.isMac ? "⌘" : "Ctrl",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
commandLabel() {
|
||||||
|
const cmd = HEYNOTE_COMMANDS[this.command]
|
||||||
|
if (cmd) {
|
||||||
|
return `${cmd.category}: ${cmd.description}`
|
||||||
|
}
|
||||||
|
return HEYNOTE_COMMANDS[this.command]?.description ||this.command
|
||||||
|
},
|
||||||
|
|
||||||
|
className() {
|
||||||
|
return this.isDefault ? "keybind-default" : "keybind-user"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr :class="className">
|
||||||
|
<td class="source">
|
||||||
|
{{ source }}
|
||||||
|
</td>
|
||||||
|
<td class="key">
|
||||||
|
<template v-if="keys">
|
||||||
|
{{ formattedKeys }}
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
<td class="command">
|
||||||
|
<span class="command-name">{{ commandLabel }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<button
|
||||||
|
v-if="!isDefault"
|
||||||
|
@click="$emit('delete')"
|
||||||
|
class="delete"
|
||||||
|
>Delete</button>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td v-if="!isDefault" class="drag-handle"></td>
|
||||||
|
<td v-else></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
tr
|
||||||
|
&.overridden
|
||||||
|
text-decoration: line-through
|
||||||
|
color: rgba(0,0,0, 0.4)
|
||||||
|
+dark-mode
|
||||||
|
color: rgba(255,255,255, 0.4)
|
||||||
|
td
|
||||||
|
&.key
|
||||||
|
//letter-spacing: 1px
|
||||||
|
&.command
|
||||||
|
//
|
||||||
|
&.drag-handle
|
||||||
|
width: 24px
|
||||||
|
padding: 0
|
||||||
|
cursor: ns-resize
|
||||||
|
background-color: rgba(0,0,0, 0.02)
|
||||||
|
background-size: 20px
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: center center
|
||||||
|
background-image: url(@/assets/icons/drag-vertical-light.svg)
|
||||||
|
+dark-mode
|
||||||
|
background-color: rgba(0,0,0, 0.08)
|
||||||
|
background-image: url(@/assets/icons/drag-vertical-dark.svg)
|
||||||
|
&:hover
|
||||||
|
background-color: rgba(0,0,0, 0.05)
|
||||||
|
+dark-mode
|
||||||
|
background-color: rgba(0,0,0, 0.25)
|
||||||
|
button.delete
|
||||||
|
padding: 0 10px
|
||||||
|
height: 22px
|
||||||
|
font-size: 12px
|
||||||
|
background: none
|
||||||
|
border: none
|
||||||
|
border-radius: 2px
|
||||||
|
cursor: pointer
|
||||||
|
background: #ddd
|
||||||
|
&:hover
|
||||||
|
background: #ccc
|
||||||
|
+dark-mode
|
||||||
|
background: #555
|
||||||
|
color: #fff
|
||||||
|
&:hover
|
||||||
|
background: #666
|
||||||
|
</style>
|
200
src/components/settings/KeyboardBindings.vue
Normal file
200
src/components/settings/KeyboardBindings.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<script>
|
||||||
|
import { mapState} from 'pinia'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
import { DEFAULT_KEYMAP, EMACS_KEYMAP } from "@/src/editor/keymap"
|
||||||
|
import { useSettingsStore } from "@/src/stores/settings-store"
|
||||||
|
import KeyBindRow from "./KeyBindRow.vue"
|
||||||
|
import AddKeyBind from "./AddKeyBind.vue"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
"userKeys",
|
||||||
|
"modelValue",
|
||||||
|
],
|
||||||
|
components: {
|
||||||
|
draggable,
|
||||||
|
KeyBindRow,
|
||||||
|
AddKeyBind,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keymap: this.modelValue,
|
||||||
|
addKeyBinding: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
addKeyBinding(newValue) {
|
||||||
|
this.$emit("addKeyBindingDialogVisible", newValue)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState(useSettingsStore, [
|
||||||
|
"settings",
|
||||||
|
]),
|
||||||
|
|
||||||
|
fixedKeymap() {
|
||||||
|
const defaultKeymap = (this.settings.keymap === "emacs" ? EMACS_KEYMAP : []).map((km) => ({
|
||||||
|
key: km.key,
|
||||||
|
command: km.command,
|
||||||
|
isDefault: true,
|
||||||
|
source: "Emacs",
|
||||||
|
}))
|
||||||
|
return defaultKeymap.concat(
|
||||||
|
DEFAULT_KEYMAP.map((km) => ({
|
||||||
|
key: km.key,
|
||||||
|
command: km.command,
|
||||||
|
isDefault: true,
|
||||||
|
source: "Default",
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onDragEnd(event) {
|
||||||
|
this.$emit("update:modelValue", this.keymap)
|
||||||
|
},
|
||||||
|
|
||||||
|
onSaveKeyBinding(event) {
|
||||||
|
this.keymap = [
|
||||||
|
{
|
||||||
|
key: event.key,
|
||||||
|
command: event.command,
|
||||||
|
},
|
||||||
|
...(this.keymap ? this.keymap : []),
|
||||||
|
]
|
||||||
|
//console.log("keymap", this.keymap)
|
||||||
|
this.$emit("update:modelValue", this.keymap)
|
||||||
|
this.addKeyBinding = false
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteKeyBinding(index) {
|
||||||
|
this.keymap = this.keymap.toSpliced(index, 1)
|
||||||
|
this.$emit("update:modelValue", this.keymap)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header" :inert="addKeyBinding">
|
||||||
|
<h2>Keyboard Bindings</h2>
|
||||||
|
<!--<p>User key bindings can be reordered. Bindings that appear first take precedence</p>-->
|
||||||
|
<div class="button-container">
|
||||||
|
<button
|
||||||
|
class="add-keybinding"
|
||||||
|
@click="addKeyBinding = !addKeyBinding"
|
||||||
|
>Add Keybinding</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AddKeyBind
|
||||||
|
v-if="addKeyBinding"
|
||||||
|
@close="addKeyBinding = false"
|
||||||
|
@save="onSaveKeyBinding"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<table :inert="addKeyBinding">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Key</th>
|
||||||
|
<th>Command</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<draggable
|
||||||
|
v-model="keymap"
|
||||||
|
tag="tbody"
|
||||||
|
group="keymap"
|
||||||
|
ghost-class="ghost"
|
||||||
|
handle=".drag-handle"
|
||||||
|
@start="drag=true"
|
||||||
|
@end="onDragEnd"
|
||||||
|
item-key="key"
|
||||||
|
>
|
||||||
|
<template #item="{element, index}">
|
||||||
|
<KeyBindRow
|
||||||
|
:keys="element.key"
|
||||||
|
:command="element.command"
|
||||||
|
:isDefault="element.isDefault"
|
||||||
|
:index="index"
|
||||||
|
@delete="deleteKeyBinding(index)"
|
||||||
|
source="User"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
<tbody>
|
||||||
|
<KeyBindRow
|
||||||
|
v-for="key in fixedKeymap"
|
||||||
|
:key="key.source + '_' + key.key"
|
||||||
|
:keys="key.key"
|
||||||
|
:command="key.command"
|
||||||
|
:isDefault="key.isDefault"
|
||||||
|
:source="key.source"
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.header
|
||||||
|
display: flex
|
||||||
|
margin-bottom: 12px
|
||||||
|
h2
|
||||||
|
flex-grow: 1
|
||||||
|
font-weight: 600
|
||||||
|
font-size: 14px
|
||||||
|
margin: 0
|
||||||
|
.button-container
|
||||||
|
.add-keybinding
|
||||||
|
font-size: 12px
|
||||||
|
height: 26px
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
|
||||||
|
table
|
||||||
|
width: 100%
|
||||||
|
background: #f1f1f1
|
||||||
|
border: 2px solid #f1f1f1
|
||||||
|
+dark-mode
|
||||||
|
background: #3c3c3c
|
||||||
|
background: #333
|
||||||
|
border: 2px solid #3c3c3c
|
||||||
|
::v-deep(tr)
|
||||||
|
background: #fff
|
||||||
|
border-bottom: 2px solid #f1f1f1
|
||||||
|
+dark-mode
|
||||||
|
background: #333
|
||||||
|
border-bottom: 2px solid #3c3c3c
|
||||||
|
&.ghost
|
||||||
|
background: #48b57e
|
||||||
|
color: #fff
|
||||||
|
+dark-mode
|
||||||
|
background: #1b6540
|
||||||
|
th
|
||||||
|
text-align: left
|
||||||
|
font-weight: 600
|
||||||
|
th, td
|
||||||
|
padding: 8px
|
||||||
|
&.actions
|
||||||
|
padding: 6px
|
||||||
|
button
|
||||||
|
height: 20px
|
||||||
|
font-size: 11px
|
||||||
|
|
||||||
|
tbody
|
||||||
|
margin-bottom: 20px
|
||||||
|
|
||||||
|
</style>
|
88
src/components/settings/RecordKeyInput.vue
Normal file
88
src/components/settings/RecordKeyInput.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script>
|
||||||
|
import { keyName, base } from "w3c-keyname"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
"modelValue",
|
||||||
|
],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keys: this.modelValue ? this.modelValue.split(" ") : [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
key() {
|
||||||
|
return this.keys.join(" ")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
modelValue(newValue) {
|
||||||
|
this.keys = this.modelValue ? this.modelValue.split(" ") : []
|
||||||
|
},
|
||||||
|
key(newValue) {
|
||||||
|
this.$emit("update:model-value", newValue)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeyDown(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
//console.log("event", event, event.code, keyName(event))
|
||||||
|
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
this.$emit("enter")
|
||||||
|
} else if (event.key === "Escape") {
|
||||||
|
if (this.keys.length > 0) {
|
||||||
|
this.keys = []
|
||||||
|
} else {
|
||||||
|
// setTimeout is used to ensure that the settings dialog's keydown listener
|
||||||
|
// doesn't close the whole settings dialog
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit("close")
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
} else if (["Alt", "Control", "Meta", "Shift"].includes(event.key)) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (this.keys.length >= 2) {
|
||||||
|
this.keys = []
|
||||||
|
}
|
||||||
|
let keyCombo = ""
|
||||||
|
if (event.altKey) {
|
||||||
|
keyCombo += "Alt-"
|
||||||
|
}
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
keyCombo += "Control-"
|
||||||
|
}
|
||||||
|
if (event.metaKey) {
|
||||||
|
keyCombo += "Meta-"
|
||||||
|
}
|
||||||
|
if (event.shiftKey) {
|
||||||
|
keyCombo += "Shift-"
|
||||||
|
}
|
||||||
|
let key = base[event.keyCode]
|
||||||
|
if (key) {
|
||||||
|
if (key === " ") {
|
||||||
|
key = "Space"
|
||||||
|
}
|
||||||
|
keyCombo += key
|
||||||
|
this.keys.push(keyCombo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:value="key"
|
||||||
|
@keydown.prevent="onKeyDown"
|
||||||
|
class="keys"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
</template>
|
@ -1,9 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { toRaw} from 'vue';
|
||||||
import { LANGUAGES } from '../../editor/languages.js'
|
import { LANGUAGES } from '../../editor/languages.js'
|
||||||
|
|
||||||
import KeyboardHotkey from "./KeyboardHotkey.vue"
|
import KeyboardHotkey from "./KeyboardHotkey.vue"
|
||||||
import TabListItem from "./TabListItem.vue"
|
import TabListItem from "./TabListItem.vue"
|
||||||
import TabContent from "./TabContent.vue"
|
import TabContent from "./TabContent.vue"
|
||||||
|
import KeyboardBindings from './KeyboardBindings.vue'
|
||||||
|
|
||||||
const defaultFontFamily = window.heynote.defaultFontFamily
|
const defaultFontFamily = window.heynote.defaultFontFamily
|
||||||
const defaultFontSize = window.heynote.defaultFontSize
|
const defaultFontSize = window.heynote.defaultFontSize
|
||||||
@ -20,15 +22,18 @@
|
|||||||
KeyboardHotkey,
|
KeyboardHotkey,
|
||||||
TabListItem,
|
TabListItem,
|
||||||
TabContent,
|
TabContent,
|
||||||
|
KeyboardBindings,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
//console.log("settings:", this.initialSettings)
|
||||||
return {
|
return {
|
||||||
keymaps: [
|
keymaps: [
|
||||||
{ name: "Default", value: "default" },
|
{ name: "Default", value: "default" },
|
||||||
{ name: "Emacs", value: "emacs" },
|
{ name: "Emacs", value: "emacs" },
|
||||||
],
|
],
|
||||||
keymap: this.initialSettings.keymap,
|
keymap: this.initialSettings.keymap,
|
||||||
|
keyBindings: this.initialSettings.keyBindings,
|
||||||
metaKey: this.initialSettings.emacsMetaKey,
|
metaKey: this.initialSettings.emacsMetaKey,
|
||||||
isMac: window.heynote.platform.isMac,
|
isMac: window.heynote.platform.isMac,
|
||||||
showLineNumberGutter: this.initialSettings.showLineNumberGutter,
|
showLineNumberGutter: this.initialSettings.showLineNumberGutter,
|
||||||
@ -63,28 +68,36 @@
|
|||||||
defaultFontSize: defaultFontSize,
|
defaultFontSize: defaultFontSize,
|
||||||
appVersion: "",
|
appVersion: "",
|
||||||
theme: this.themeSetting,
|
theme: this.themeSetting,
|
||||||
|
|
||||||
|
// tracks if the add key binding dialog is visible (so that we can set inert on the save button)
|
||||||
|
addKeyBindingDialogVisible: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
window.addEventListener("keydown", this.onKeyDown);
|
||||||
|
|
||||||
|
this.appVersion = await window.heynote.getVersion()
|
||||||
|
|
||||||
if (window.queryLocalFonts !== undefined) {
|
if (window.queryLocalFonts !== undefined) {
|
||||||
let localFonts = [... new Set((await window.queryLocalFonts()).map(f => f.family))].filter(f => f !== "Hack")
|
let localFonts = [... new Set((await window.queryLocalFonts()).map(f => f.family))].filter(f => f !== "Hack")
|
||||||
localFonts = [...new Set(localFonts)].map(f => [f, f])
|
localFonts = [...new Set(localFonts)].map(f => [f, f])
|
||||||
this.systemFonts = [[defaultFontFamily, defaultFontFamily + " (default)"], ...localFonts]
|
this.systemFonts = [[defaultFontFamily, defaultFontFamily + " (default)"], ...localFonts]
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", this.onKeyDown);
|
|
||||||
this.$refs.keymapSelector.focus()
|
|
||||||
|
|
||||||
this.appVersion = await window.heynote.getVersion()
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("keydown", this.onKeyDown);
|
window.removeEventListener("keydown", this.onKeyDown);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
keyBindings(newKeyBindings) {
|
||||||
|
this.updateSettings()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape" && !this.addKeyBindingDialogVisible) {
|
||||||
this.$emit("closeSettings")
|
this.$emit("closeSettings")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -94,6 +107,7 @@
|
|||||||
showLineNumberGutter: this.showLineNumberGutter,
|
showLineNumberGutter: this.showLineNumberGutter,
|
||||||
showFoldGutter: this.showFoldGutter,
|
showFoldGutter: this.showFoldGutter,
|
||||||
keymap: this.keymap,
|
keymap: this.keymap,
|
||||||
|
keyBindings: this.keyBindings.map((kb) => toRaw(kb)),
|
||||||
emacsMetaKey: window.heynote.platform.isMac ? this.metaKey : "alt",
|
emacsMetaKey: window.heynote.platform.isMac ? this.metaKey : "alt",
|
||||||
allowBetaVersions: this.allowBetaVersions,
|
allowBetaVersions: this.allowBetaVersions,
|
||||||
enableGlobalHotkey: this.enableGlobalHotkey,
|
enableGlobalHotkey: this.enableGlobalHotkey,
|
||||||
@ -161,6 +175,12 @@
|
|||||||
:activeTab="activeTab"
|
:activeTab="activeTab"
|
||||||
@click="activeTab = 'appearance'"
|
@click="activeTab = 'appearance'"
|
||||||
/>
|
/>
|
||||||
|
<TabListItem
|
||||||
|
name="Key Bindings"
|
||||||
|
tab="keyboard-bindings"
|
||||||
|
:activeTab="activeTab"
|
||||||
|
@click="activeTab = 'keyboard-bindings'"
|
||||||
|
/>
|
||||||
<TabListItem
|
<TabListItem
|
||||||
:name="isWebApp ? 'Version' : 'Updates'"
|
:name="isWebApp ? 'Version' : 'Updates'"
|
||||||
tab="updates"
|
tab="updates"
|
||||||
@ -171,23 +191,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<TabContent tab="general" :activeTab="activeTab">
|
<TabContent tab="general" :activeTab="activeTab">
|
||||||
<div class="row">
|
|
||||||
<div class="entry">
|
|
||||||
<h2>Keymap</h2>
|
|
||||||
<select ref="keymapSelector" v-model="keymap" @change="updateSettings" class="keymap">
|
|
||||||
<template v-for="km in keymaps" :key="km.value">
|
|
||||||
<option :selected="km.value === keymap" :value="km.value">{{ km.name }}</option>
|
|
||||||
</template>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="entry" v-if="keymap === 'emacs' && isMac">
|
|
||||||
<h2>Meta Key</h2>
|
|
||||||
<select v-model="metaKey" @change="updateSettings" class="metaKey">
|
|
||||||
<option :selected="metaKey === 'meta'" value="meta">Command</option>
|
|
||||||
<option :selected="metaKey === 'alt'" value="alt">Option</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row" v-if="!isWebApp">
|
<div class="row" v-if="!isWebApp">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<h2>Global Keyboard Shortcut</h2>
|
<h2>Global Keyboard Shortcut</h2>
|
||||||
@ -244,14 +247,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row" v-if="!isWebApp">
|
<div class="row" v-if="!isWebApp">
|
||||||
<div class="entry buffer-location">
|
<div class="entry buffer-location">
|
||||||
<h2>Buffer File Path</h2>
|
<h2>Buffer Files Path</h2>
|
||||||
<label class="keyboard-shortcut-label">
|
<label class="keyboard-shortcut-label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="customBufferLocation"
|
v-model="customBufferLocation"
|
||||||
@change="onCustomBufferLocationChange"
|
@change="onCustomBufferLocationChange"
|
||||||
/>
|
/>
|
||||||
Use custom buffer file location
|
Use custom location for the buffer files
|
||||||
</label>
|
</label>
|
||||||
<div class="file-path">
|
<div class="file-path">
|
||||||
<button
|
<button
|
||||||
@ -369,6 +372,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|
||||||
|
<TabContent tab="keyboard-bindings" :activeTab="activeTab">
|
||||||
|
<div class="row">
|
||||||
|
<div class="entry">
|
||||||
|
<h2>Keymap</h2>
|
||||||
|
<select v-model="keymap" @change="updateSettings" class="keymap">
|
||||||
|
<template v-for="km in keymaps" :key="km.value">
|
||||||
|
<option :selected="km.value === keymap" :value="km.value">{{ km.name }}</option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="entry" v-if="keymap === 'emacs' && isMac">
|
||||||
|
<h2>Meta Key</h2>
|
||||||
|
<select v-model="metaKey" @change="updateSettings" class="metaKey">
|
||||||
|
<option :selected="metaKey === 'meta'" value="meta">Command</option>
|
||||||
|
<option :selected="metaKey === 'alt'" value="alt">Option</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<KeyboardBindings
|
||||||
|
:userKeys="keyBindings ? keyBindings : {}"
|
||||||
|
v-model="keyBindings"
|
||||||
|
@addKeyBindingDialogVisible="addKeyBindingDialogVisible = $event"
|
||||||
|
/>
|
||||||
|
</TabContent>
|
||||||
|
|
||||||
<TabContent tab="updates" :activeTab="activeTab">
|
<TabContent tab="updates" :activeTab="activeTab">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
@ -407,7 +435,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar" :inert="addKeyBindingDialogVisible">
|
||||||
<button
|
<button
|
||||||
@click="$emit('closeSettings')"
|
@click="$emit('closeSettings')"
|
||||||
class="close"
|
class="close"
|
||||||
@ -436,14 +464,16 @@
|
|||||||
background: rgba(0, 0, 0, 0.5)
|
background: rgba(0, 0, 0, 0.5)
|
||||||
|
|
||||||
.dialog
|
.dialog
|
||||||
|
--dialog-height: 600px
|
||||||
|
--bottom-bar-height: 48px
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
z-index: 2
|
z-index: 2
|
||||||
position: absolute
|
position: absolute
|
||||||
left: 50%
|
left: 50%
|
||||||
top: 50%
|
top: 50%
|
||||||
transform: translate(-50%, -50%)
|
transform: translate(-50%, -50%)
|
||||||
width: 700px
|
width: 820px
|
||||||
height: 560px
|
height: var(--dialog-height)
|
||||||
max-width: 100%
|
max-width: 100%
|
||||||
max-height: 100%
|
max-height: 100%
|
||||||
display: flex
|
display: flex
|
||||||
@ -463,6 +493,7 @@
|
|||||||
.dialog-content
|
.dialog-content
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
display: flex
|
display: flex
|
||||||
|
height: calc(var(--dialog-height) - var(--bottom-bar-height))
|
||||||
.sidebar
|
.sidebar
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
width: 140px
|
width: 140px
|
||||||
@ -480,6 +511,7 @@
|
|||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
padding: 40px
|
padding: 40px
|
||||||
overflow-y: auto
|
overflow-y: auto
|
||||||
|
position: relative
|
||||||
select
|
select
|
||||||
height: 22px
|
height: 22px
|
||||||
margin: 4px 0
|
margin: 4px 0
|
||||||
@ -536,6 +568,8 @@
|
|||||||
background: #222
|
background: #222
|
||||||
color: #aaa
|
color: #aaa
|
||||||
.bottom-bar
|
.bottom-bar
|
||||||
|
box-sizing: border-box
|
||||||
|
height: var(--bottom-bar-height)
|
||||||
border-radius: 0 0 5px 5px
|
border-radius: 0 0 5px 5px
|
||||||
background: #eee
|
background: #eee
|
||||||
text-align: right
|
text-align: right
|
||||||
@ -544,4 +578,5 @@
|
|||||||
background: #222
|
background: #222
|
||||||
.close
|
.close
|
||||||
height: 28px
|
height: 28px
|
||||||
|
cursor: pointer
|
||||||
</style>
|
</style>
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
li
|
li
|
||||||
padding: 9px 20px
|
padding: 9px 20px
|
||||||
font-size: 13px
|
font-size: 13px
|
||||||
|
line-height: 1.3
|
||||||
user-select: none
|
user-select: none
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
&:hover
|
&:hover
|
||||||
@ -25,10 +26,11 @@
|
|||||||
+dark-mode
|
+dark-mode
|
||||||
background: #292929
|
background: #292929
|
||||||
&.active
|
&.active
|
||||||
background: #48b57e
|
background: var(--highlight-color)
|
||||||
color: #fff
|
color: #fff
|
||||||
cursor: default
|
cursor: default
|
||||||
+dark-mode
|
+dark-mode
|
||||||
background: #1b6540
|
// needed for specificity (to not be overridden by :hover in dark mode)
|
||||||
|
background: var(--highlight-color)
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -1,3 +1,4 @@
|
|||||||
@import "reset"
|
@use "reset"
|
||||||
@import "font"
|
@use "font"
|
||||||
@import "base"
|
@use "base"
|
||||||
|
@use "autocomplete"
|
||||||
|
45
src/css/autocomplete.sass
Normal file
45
src/css/autocomplete.sass
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
@use "@/src/css/include.sass" as *
|
||||||
|
|
||||||
|
|
||||||
|
.p-component
|
||||||
|
--p-inputtext-background: #fff
|
||||||
|
--p-inputtext-border-color: #ccc
|
||||||
|
--p-inputtext-border-radius: 3px
|
||||||
|
--p-inputtext-padding-y: 4px
|
||||||
|
--p-inputtext-padding-x: 4px
|
||||||
|
--p-inputtext-focus-border-color: var(--p-inputtext-border-color)
|
||||||
|
--p-inputtext-hover-border-color: var(--p-inputtext-border-color)
|
||||||
|
--p-inputtext-active-border-color: var(--p-inputtext-border-color)
|
||||||
|
|
||||||
|
--p-autocomplete-dropdown-border-color: var(--p-inputtext-border-color)
|
||||||
|
--p-autocomplete-dropdown-border-radius: var(--p-inputtext-border-radius)
|
||||||
|
--p-autocomplete-dropdown-hover-border-color: var(--p-inputtext-hover-border-color)
|
||||||
|
--p-autocomplete-dropdown-active-border-color: var(--p-inputtext-active-border-color)
|
||||||
|
--p-autocomplete-dropdown-hover-background: #f1f1f1
|
||||||
|
--p-autocomplete-option-focus-background: var(--highlight-color)
|
||||||
|
--p-autocomplete-option-focus-color: #fff
|
||||||
|
|
||||||
|
+dark-mode
|
||||||
|
--p-inputtext-background: #202020
|
||||||
|
--p-inputtext-border-color: #444
|
||||||
|
--p-autocomplete-dropdown-hover-background: #2a2a2a
|
||||||
|
|
||||||
|
.p-inputtext.p-inputtext
|
||||||
|
font-size: 12px
|
||||||
|
|
||||||
|
.p-autocomplete-list-container
|
||||||
|
background: #fff
|
||||||
|
border: 1px solid #f1f1f1
|
||||||
|
padding: 6px 0
|
||||||
|
border-radius: 3px
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)
|
||||||
|
+dark-mode
|
||||||
|
color: rgba(255,255,255, 0.8)
|
||||||
|
background: #333
|
||||||
|
border: 1px solid #2a2a2a
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3)
|
||||||
|
|
||||||
|
.p-autocomplete-list > li
|
||||||
|
padding: 6px 10px
|
||||||
|
b
|
||||||
|
font-weight: bold
|
@ -1,10 +1,14 @@
|
|||||||
|
@use "include" as *
|
||||||
|
|
||||||
:root[theme='light']
|
:root[theme='light']
|
||||||
--status-bar-background: #48b57e
|
--status-bar-background: #48b57e
|
||||||
--status-bar-color: #fff
|
--status-bar-color: #fff
|
||||||
|
--highlight-color: #48b57e
|
||||||
|
|
||||||
:root[theme='dark']
|
:root[theme='dark']
|
||||||
--status-bar-background: #0e1217
|
--status-bar-background: #0e1217
|
||||||
--status-bar-color: rgba(255, 255, 255, 0.75)
|
--status-bar-color: rgba(255, 255, 255, 0.75)
|
||||||
|
--highlight-color: #1b6540
|
||||||
|
|
||||||
html
|
html
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
font-weight: 400
|
font-weight: 400
|
||||||
font-style: normal
|
font-style: normal
|
||||||
|
|
||||||
|
|
||||||
@font-face
|
@font-face
|
||||||
font-family: 'Hack'
|
font-family: 'Hack'
|
||||||
src: url('@/assets/font/hack/hack-bold.woff2') format('woff2'), url('@/assets/font/hack/hack-bold.woff') format('woff')
|
src: url('@/assets/font/hack/hack-bold.woff2') format('woff2'), url('@/assets/font/hack/hack-bold.woff') format('woff')
|
||||||
@ -23,5 +22,3 @@
|
|||||||
font-weight: 700
|
font-weight: 700
|
||||||
font-style: italic
|
font-style: italic
|
||||||
|
|
||||||
|
|
||||||
@import "@/assets/font/open-sans/open-sans"
|
|
||||||
|
@ -8,3 +8,10 @@ export const ADD_NEW_BLOCK = "heynote-add-new-block"
|
|||||||
export const DELETE_BLOCK = "heynote-delete-block"
|
export const DELETE_BLOCK = "heynote-delete-block"
|
||||||
export const CURSOR_CHANGE = "heynote-cursor-change"
|
export const CURSOR_CHANGE = "heynote-cursor-change"
|
||||||
export const APPEND_BLOCK = "heynote-append-block"
|
export const APPEND_BLOCK = "heynote-append-block"
|
||||||
|
export const SET_FONT = "heynote-set-font"
|
||||||
|
|
||||||
|
|
||||||
|
// This function checks if any of the transactions has the given Heynote annotation
|
||||||
|
export function transactionsHasAnnotation(transactions, annotation) {
|
||||||
|
return transactions.some(tr => tr.annotation(heynoteEvent) === annotation)
|
||||||
|
}
|
||||||
|
@ -360,6 +360,7 @@ export const deleteBlock = (editor) => ({state, dispatch}) => {
|
|||||||
selection: EditorSelection.cursor(newSelection),
|
selection: EditorSelection.cursor(newSelection),
|
||||||
annotations: [heynoteEvent.of(DELETE_BLOCK)],
|
annotations: [heynoteEvent.of(DELETE_BLOCK)],
|
||||||
}))
|
}))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteBlockSetCursorPreviousBlock = (editor) => ({state, dispatch}) => {
|
export const deleteBlockSetCursorPreviousBlock = (editor) => ({state, dispatch}) => {
|
||||||
@ -380,4 +381,5 @@ export const deleteBlockSetCursorPreviousBlock = (editor) => ({state, dispatch})
|
|||||||
selection: EditorSelection.cursor(newSelection),
|
selection: EditorSelection.cursor(newSelection),
|
||||||
annotations: [heynoteEvent.of(DELETE_BLOCK)],
|
annotations: [heynoteEvent.of(DELETE_BLOCK)],
|
||||||
}))
|
}))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EditorSelection } from "@codemirror/state"
|
import { EditorSelection } from "@codemirror/state"
|
||||||
import { getActiveNoteBlock } from "./block"
|
import { getNoteBlockFromPos } from "./block"
|
||||||
|
|
||||||
function updateSel(sel, by) {
|
function updateSel(sel, by) {
|
||||||
return EditorSelection.create(sel.ranges.map(by), sel.mainIndex);
|
return EditorSelection.create(sel.ranges.map(by), sel.mainIndex);
|
||||||
@ -28,10 +28,10 @@ export const deleteLine = (view) => {
|
|||||||
|
|
||||||
const { state } = view
|
const { state } = view
|
||||||
|
|
||||||
const block = getActiveNoteBlock(view.state)
|
|
||||||
const selectedLines = selectedLineBlocks(state)
|
const selectedLines = selectedLineBlocks(state)
|
||||||
|
|
||||||
const changes = state.changes(selectedLines.map(({ from, to }) => {
|
const changes = state.changes(selectedLines.map(({ from, to }) => {
|
||||||
|
const block = getNoteBlockFromPos(state, from)
|
||||||
if(from !== block.content.from || to !== block.content.to) {
|
if(from !== block.content.from || to !== block.content.to) {
|
||||||
if (from > 0) from--
|
if (from > 0) from--
|
||||||
else if (to < state.doc.length) to++
|
else if (to < state.doc.length) to++
|
||||||
|
@ -4,7 +4,7 @@ import { RangeSetBuilder } from "@codemirror/state"
|
|||||||
import { WidgetType } from "@codemirror/view"
|
import { WidgetType } from "@codemirror/view"
|
||||||
|
|
||||||
import { getNoteBlockFromPos } from "./block"
|
import { getNoteBlockFromPos } from "./block"
|
||||||
import { CURRENCIES_LOADED } from "../annotation"
|
import { transactionsHasAnnotation, CURRENCIES_LOADED } from "../annotation"
|
||||||
|
|
||||||
|
|
||||||
class MathResult extends WidgetType {
|
class MathResult extends WidgetType {
|
||||||
@ -107,12 +107,6 @@ function mathDeco(view) {
|
|||||||
return builder.finish()
|
return builder.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This function checks if any of the transactions has the given annotation
|
|
||||||
const transactionsHasAnnotation = (transactions, annotation) => {
|
|
||||||
return transactions.some(tr => tr.annotations.some(a => a.value === annotation))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mathBlock = ViewPlugin.fromClass(class {
|
export const mathBlock = ViewPlugin.fromClass(class {
|
||||||
decorations
|
decorations
|
||||||
|
|
||||||
|
30
src/editor/block/transpose-chars.js
Normal file
30
src/editor/block/transpose-chars.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { EditorSelection, findClusterBreak} from "@codemirror/state";
|
||||||
|
|
||||||
|
import { getNoteBlockFromPos } from "./block"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Flip the characters before and after the cursor(s).
|
||||||
|
*/
|
||||||
|
export const transposeChars = ({ state, dispatch }) => {
|
||||||
|
if (state.readOnly)
|
||||||
|
return false;
|
||||||
|
let changes = state.changeByRange(range => {
|
||||||
|
// prevent transposing characters if we're at the start or end of a block, since it'll break the block syntax
|
||||||
|
const block = getNoteBlockFromPos(state, range.from)
|
||||||
|
if (range.from === block.content.from || range.from === block.content.to) {
|
||||||
|
return { range }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!range.empty || range.from == 0 || range.from == state.doc.length)
|
||||||
|
return { range };
|
||||||
|
let pos = range.from, line = state.doc.lineAt(pos);
|
||||||
|
let from = pos == line.from ? pos - 1 : findClusterBreak(line.text, pos - line.from, false) + line.from;
|
||||||
|
let to = pos == line.to ? pos + 1 : findClusterBreak(line.text, pos - line.from, true) + line.from;
|
||||||
|
return { changes: { from, to, insert: state.doc.slice(pos, to).append(state.doc.slice(from, pos)) },
|
||||||
|
range: EditorSelection.cursor(to) };
|
||||||
|
});
|
||||||
|
if (changes.changes.empty)
|
||||||
|
return false;
|
||||||
|
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "move.character" }));
|
||||||
|
return true;
|
||||||
|
};
|
10
src/editor/close-brackets.js
Normal file
10
src/editor/close-brackets.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'
|
||||||
|
import { Prec } from "@codemirror/state"
|
||||||
|
import { keymap } from "@codemirror/view"
|
||||||
|
|
||||||
|
export function getCloseBracketsExtensions() {
|
||||||
|
return [
|
||||||
|
closeBrackets(),
|
||||||
|
Prec.highest(keymap.of(closeBracketsKeymap)),
|
||||||
|
]
|
||||||
|
}
|
167
src/editor/commands.js
Normal file
167
src/editor/commands.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import * as codeMirrorCommands from "@codemirror/commands"
|
||||||
|
import {
|
||||||
|
undo, redo,
|
||||||
|
indentMore, indentLess,
|
||||||
|
deleteCharBackward, deleteCharForward,
|
||||||
|
deleteGroupBackward, deleteGroupForward,
|
||||||
|
deleteLineBoundaryBackward, deleteLineBoundaryForward,
|
||||||
|
deleteToLineEnd, deleteToLineStart,
|
||||||
|
simplifySelection,
|
||||||
|
splitLine,
|
||||||
|
insertNewlineAndIndent,
|
||||||
|
} from "@codemirror/commands"
|
||||||
|
import { foldCode, unfoldCode } from "@codemirror/language"
|
||||||
|
import { selectNextOccurrence } from "@codemirror/search"
|
||||||
|
import { insertNewlineContinueMarkup } from "@codemirror/lang-markdown"
|
||||||
|
|
||||||
|
import {
|
||||||
|
addNewBlockAfterCurrent, addNewBlockBeforeCurrent, addNewBlockAfterLast, addNewBlockBeforeFirst, insertNewBlockAtCursor,
|
||||||
|
gotoPreviousBlock, gotoNextBlock, selectNextBlock, selectPreviousBlock,
|
||||||
|
gotoPreviousParagraph, gotoNextParagraph, selectNextParagraph, selectPreviousParagraph,
|
||||||
|
moveLineUp, moveLineDown,
|
||||||
|
selectAll,
|
||||||
|
deleteBlock, deleteBlockSetCursorPreviousBlock,
|
||||||
|
newCursorBelow, newCursorAbove,
|
||||||
|
} from "./block/commands.js"
|
||||||
|
import { deleteLine } from "./block/delete-line.js"
|
||||||
|
import { formatBlockContent } from "./block/format-code.js"
|
||||||
|
import { transposeChars } from "./block/transpose-chars.js"
|
||||||
|
|
||||||
|
import { cutCommand, copyCommand, pasteCommand } from "./copy-paste.js"
|
||||||
|
|
||||||
|
import { markModeMoveCommand, toggleSelectionMarkMode, selectionMarkModeCancel } from "./mark-mode.js"
|
||||||
|
|
||||||
|
|
||||||
|
const cursorPreviousBlock = markModeMoveCommand(gotoPreviousBlock, selectPreviousBlock)
|
||||||
|
const cursorNextBlock = markModeMoveCommand(gotoNextBlock, selectNextBlock)
|
||||||
|
const cursorPreviousParagraph = markModeMoveCommand(gotoPreviousParagraph, selectPreviousParagraph)
|
||||||
|
const cursorNextParagraph = markModeMoveCommand(gotoNextParagraph, selectNextParagraph)
|
||||||
|
|
||||||
|
|
||||||
|
const openLanguageSelector = (editor) => () => {
|
||||||
|
editor.openLanguageSelector()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const openBufferSelector = (editor) => () => {
|
||||||
|
editor.openBufferSelector()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const openCommandPalette = (editor) => () => {
|
||||||
|
editor.openCommandPalette()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const openMoveToBuffer = (editor) => () => {
|
||||||
|
editor.openMoveToBufferSelector()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const openCreateNewBuffer = (editor) => () => {
|
||||||
|
editor.openCreateBuffer("new")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const nothing = (view) => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd = (f, category, description) => ({
|
||||||
|
run: f,
|
||||||
|
name: f.name,
|
||||||
|
description: description,
|
||||||
|
category: category,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cmdLessContext = (f, category, description) => ({
|
||||||
|
run: (editor) => f,
|
||||||
|
name: f.name,
|
||||||
|
description: description,
|
||||||
|
category: category,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const HEYNOTE_COMMANDS = {
|
||||||
|
addNewBlockAfterCurrent: cmd(addNewBlockAfterCurrent, "Block", "Add new block after current block"),
|
||||||
|
addNewBlockBeforeCurrent: cmd(addNewBlockBeforeCurrent, "Block", "Add new block before current block"),
|
||||||
|
addNewBlockAfterLast: cmd(addNewBlockAfterLast, "Block", "Add new block after last block"),
|
||||||
|
addNewBlockBeforeFirst: cmd(addNewBlockBeforeFirst, "Block", "Add new block before first block"),
|
||||||
|
insertNewBlockAtCursor: cmd(insertNewBlockAtCursor, "Block", "Insert new block at cursor"),
|
||||||
|
deleteBlock: cmd(deleteBlock, "Block", "Delete block"),
|
||||||
|
deleteBlockSetCursorPreviousBlock: cmd(deleteBlockSetCursorPreviousBlock, "Block", "Delete block and set cursor to previous block"),
|
||||||
|
cursorPreviousBlock: cmd(cursorPreviousBlock, "Cursor", "Move cursor to previous block"),
|
||||||
|
cursorNextBlock: cmd(cursorNextBlock, "Cursor", "Move cursor to next block"),
|
||||||
|
cursorPreviousParagraph: cmd(cursorPreviousParagraph, "Cursor", "Move cursor to previous paragraph"),
|
||||||
|
cursorNextParagraph: cmd(cursorNextParagraph, "Cursor", "Move cursor to next paragraph"),
|
||||||
|
toggleSelectionMarkMode: cmd(toggleSelectionMarkMode, "Cursor", "Toggle selection mark mode"),
|
||||||
|
selectionMarkModeCancel: cmd(selectionMarkModeCancel, "Cursor", "Cancel selection mark mode"),
|
||||||
|
openLanguageSelector: cmd(openLanguageSelector, "Block", "Select block language"),
|
||||||
|
openBufferSelector: cmd(openBufferSelector, "Buffer", "Buffer selector"),
|
||||||
|
openCommandPalette: cmd(openCommandPalette, "Editor", "Open command palette"),
|
||||||
|
openMoveToBuffer: cmd(openMoveToBuffer, "Block", "Move block to another buffer"),
|
||||||
|
openCreateNewBuffer: cmd(openCreateNewBuffer, "Buffer", "Create new buffer"),
|
||||||
|
cut: cmd(cutCommand, "Clipboard", "Cut selection"),
|
||||||
|
copy: cmd(copyCommand, "Clipboard", "Copy selection"),
|
||||||
|
|
||||||
|
// commands without editor context
|
||||||
|
paste: cmdLessContext(pasteCommand, "Clipboard", "Paste from clipboard"),
|
||||||
|
selectAll: cmdLessContext(selectAll, "Selection", "Select all"),
|
||||||
|
moveLineUp: cmdLessContext(moveLineUp, "Edit", "Move line up"),
|
||||||
|
moveLineDown: cmdLessContext(moveLineDown, "Edit", "Move line down"),
|
||||||
|
deleteLine: cmdLessContext(deleteLine, "Edit", "Delete line"),
|
||||||
|
formatBlockContent: cmdLessContext(formatBlockContent, "Block", "Format block content"),
|
||||||
|
newCursorAbove: cmdLessContext(newCursorAbove, "Cursor", "Add cursor above"),
|
||||||
|
newCursorBelow: cmdLessContext(newCursorBelow, "Cursor", "Add cursor below"),
|
||||||
|
selectPreviousParagraph: cmdLessContext(selectPreviousParagraph, "Selection", "Select to previous paragraph"),
|
||||||
|
selectNextParagraph: cmdLessContext(selectNextParagraph, "Selection", "Select to next paragraph"),
|
||||||
|
selectPreviousBlock: cmdLessContext(selectPreviousBlock, "Selection", "Select to previous block"),
|
||||||
|
selectNextBlock: cmdLessContext(selectNextBlock, "Selection", "Select to next block"),
|
||||||
|
nothing: cmdLessContext(nothing, "Misc", "Do nothing"),
|
||||||
|
|
||||||
|
// directly from CodeMirror
|
||||||
|
undo: cmdLessContext(undo, "Edit", "Undo"),
|
||||||
|
redo: cmdLessContext(redo, "Edit", "Redo"),
|
||||||
|
indentMore: cmdLessContext(indentMore, "Edit", "Indent more"),
|
||||||
|
indentLess: cmdLessContext(indentLess, "Edit", "Indent less"),
|
||||||
|
foldCode: cmdLessContext(foldCode, "Edit", "Fold code"),
|
||||||
|
unfoldCode: cmdLessContext(unfoldCode, "Edit", "Unfold code"),
|
||||||
|
selectNextOccurrence: cmdLessContext(selectNextOccurrence, "Cursor", "Select next occurrence"),
|
||||||
|
deleteCharBackward: cmdLessContext(deleteCharBackward, "Edit", "Delete character backward"),
|
||||||
|
deleteCharForward: cmdLessContext(deleteCharForward, "Edit", "Delete character forward"),
|
||||||
|
deleteGroupBackward: cmdLessContext(deleteGroupBackward, "Edit", "Delete group backward"),
|
||||||
|
deleteGroupForward: cmdLessContext(deleteGroupForward, "Edit", "Delete group forward"),
|
||||||
|
deleteLineBoundaryBackward: cmdLessContext(deleteLineBoundaryBackward, "Edit", "Delete from start of wrapped line"),
|
||||||
|
deleteLineBoundaryForward: cmdLessContext(deleteLineBoundaryForward, "Edit", "Delete to end of wrapped line"),
|
||||||
|
deleteToLineEnd: cmdLessContext(deleteToLineEnd, "Edit", "Delete to end of line"),
|
||||||
|
deleteToLineStart: cmdLessContext(deleteToLineStart, "Edit", "Delete from start of line"),
|
||||||
|
simplifySelection: cmdLessContext(simplifySelection, "Cursor", "Simplify selection"),
|
||||||
|
splitLine: cmdLessContext(splitLine, "Edit", "Split line"),
|
||||||
|
transposeChars: cmdLessContext(transposeChars, "Edit", "Transpose characters"),
|
||||||
|
insertNewlineAndIndent: cmdLessContext(insertNewlineAndIndent, "Edit", "Insert newline and indent"),
|
||||||
|
insertNewlineContinueMarkup: cmdLessContext(insertNewlineContinueMarkup, "Markdown", "Insert newline and continue todo lists/block quotes"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// selection mark-mode:ify all cursor/select commands from CodeMirror
|
||||||
|
for (let commandSuffix of [
|
||||||
|
"CharLeft", "CharRight",
|
||||||
|
"CharBackward", "CharForward",
|
||||||
|
"LineUp", "LineDown",
|
||||||
|
"LineStart", "LineEnd",
|
||||||
|
"GroupLeft", "GroupRight",
|
||||||
|
"GroupForward", "GroupBackward",
|
||||||
|
"PageUp", "PageDown",
|
||||||
|
"SyntaxLeft", "SyntaxRight",
|
||||||
|
"SubwordBackward", "SubwordForward",
|
||||||
|
"LineBoundaryBackward", "LineBoundaryForward",
|
||||||
|
]) {
|
||||||
|
HEYNOTE_COMMANDS[`cursor${commandSuffix}`] = {
|
||||||
|
run: markModeMoveCommand(codeMirrorCommands[`cursor${commandSuffix}`], codeMirrorCommands[`select${commandSuffix}`]),
|
||||||
|
name: `cursor${commandSuffix}`,
|
||||||
|
description: `cursor${commandSuffix}`,
|
||||||
|
category: "Cursor",
|
||||||
|
}
|
||||||
|
HEYNOTE_COMMANDS[`select${commandSuffix}`] = {
|
||||||
|
run: (editor) => codeMirrorCommands[`select${commandSuffix}`],
|
||||||
|
name: `select${commandSuffix}`,
|
||||||
|
description: `select${commandSuffix}`,
|
||||||
|
category: "Cursor",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HEYNOTE_COMMANDS }
|
@ -2,7 +2,6 @@ import { EditorState, EditorSelection } from "@codemirror/state"
|
|||||||
import { EditorView } from "@codemirror/view"
|
import { EditorView } from "@codemirror/view"
|
||||||
|
|
||||||
import { LANGUAGES } from './languages.js';
|
import { LANGUAGES } from './languages.js';
|
||||||
import { setEmacsMarkMode } from "./emacs.js"
|
|
||||||
|
|
||||||
|
|
||||||
const languageTokensMatcher = LANGUAGES.map(l => l.token).join("|")
|
const languageTokensMatcher = LANGUAGES.map(l => l.token).join("|")
|
||||||
@ -61,7 +60,7 @@ export const heynoteCopyCut = (editor) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we're in Emacs mode, we want to exit mark mode in case we're in it
|
// if we're in Emacs mode, we want to exit mark mode in case we're in it
|
||||||
setEmacsMarkMode(false)
|
editor.selectionMarkMode = false
|
||||||
|
|
||||||
// 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 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") {
|
if (editor.deselectOnCopy && event.type == "copy") {
|
||||||
@ -95,7 +94,7 @@ const copyCut = (view, cut, editor) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we're in Emacs mode, we want to exit mark mode in case we're in it
|
// if we're in Emacs mode, we want to exit mark mode in case we're in it
|
||||||
setEmacsMarkMode(false)
|
editor.selectionMarkMode = false
|
||||||
|
|
||||||
// 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 is set (e.g. we're in Emacs mode), we want to remove the selection after we've copied the text
|
||||||
if (editor.deselectOnCopy && !cut) {
|
if (editor.deselectOnCopy && !cut) {
|
||||||
@ -107,6 +106,7 @@ const copyCut = (view, cut, editor) => {
|
|||||||
selection: newSelection,
|
selection: newSelection,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Annotation, EditorState, Compartment, Facet, EditorSelection, Transaction } from "@codemirror/state"
|
import { Annotation, EditorState, Compartment, Facet, EditorSelection, Transaction, Prec } from "@codemirror/state"
|
||||||
import { EditorView, keymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view"
|
import { EditorView, keymap as cmKeymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view"
|
||||||
import { indentUnit, forceParsing, foldGutter, ensureSyntaxTree } from "@codemirror/language"
|
import { indentUnit, forceParsing, foldGutter, ensureSyntaxTree } from "@codemirror/language"
|
||||||
import { markdown } from "@codemirror/lang-markdown"
|
import { markdown, markdownKeymap } from "@codemirror/lang-markdown"
|
||||||
import { closeBrackets } from "@codemirror/autocomplete";
|
import { closeBrackets } from "@codemirror/autocomplete";
|
||||||
import { undo, redo } from "@codemirror/commands"
|
import { undo, redo } from "@codemirror/commands"
|
||||||
|
|
||||||
@ -11,32 +11,24 @@ import { heynoteBase } from "./theme/base.js"
|
|||||||
import { getFontTheme } from "./theme/font-theme.js";
|
import { getFontTheme } from "./theme/font-theme.js";
|
||||||
import { customSetup } from "./setup.js"
|
import { customSetup } from "./setup.js"
|
||||||
import { heynoteLang } from "./lang-heynote/heynote.js"
|
import { heynoteLang } from "./lang-heynote/heynote.js"
|
||||||
|
import { getCloseBracketsExtensions } from "./close-brackets.js"
|
||||||
import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js"
|
import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js"
|
||||||
import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK } from "./annotation.js";
|
import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK, SET_FONT } from "./annotation.js";
|
||||||
import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock, selectAll } from "./block/commands.js"
|
import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock, selectAll } from "./block/commands.js"
|
||||||
import { formatBlockContent } from "./block/format-code.js"
|
import { formatBlockContent } from "./block/format-code.js"
|
||||||
import { heynoteKeymap } from "./keymap.js"
|
import { getKeymapExtensions } from "./keymap.js"
|
||||||
import { emacsKeymap } from "./emacs.js"
|
|
||||||
import { heynoteCopyCut } from "./copy-paste"
|
import { heynoteCopyCut } 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"
|
||||||
import { todoCheckboxPlugin} from "./todo-checkbox.ts"
|
import { todoCheckboxPlugin} from "./todo-checkbox.ts"
|
||||||
import { links } from "./links.js"
|
import { links } from "./links.js"
|
||||||
|
import { HEYNOTE_COMMANDS } from "./commands.js";
|
||||||
import { NoteFormat } from "../common/note-format.js"
|
import { NoteFormat } from "../common/note-format.js"
|
||||||
import { AUTO_SAVE_INTERVAL } from "../common/constants.js"
|
import { AUTO_SAVE_INTERVAL } from "../common/constants.js"
|
||||||
import { useHeynoteStore } from "../stores/heynote-store.js";
|
import { useHeynoteStore } from "../stores/heynote-store.js";
|
||||||
import { useErrorStore } from "../stores/error-store.js";
|
import { useErrorStore } from "../stores/error-store.js";
|
||||||
|
|
||||||
|
|
||||||
function getKeymapExtensions(editor, keymap) {
|
|
||||||
if (keymap === "emacs") {
|
|
||||||
return emacsKeymap(editor)
|
|
||||||
} else {
|
|
||||||
return heynoteKeymap(editor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class HeynoteEditor {
|
export class HeynoteEditor {
|
||||||
constructor({
|
constructor({
|
||||||
element,
|
element,
|
||||||
@ -54,6 +46,7 @@ export class HeynoteEditor {
|
|||||||
tabSize=4,
|
tabSize=4,
|
||||||
defaultBlockToken,
|
defaultBlockToken,
|
||||||
defaultBlockAutoDetect,
|
defaultBlockAutoDetect,
|
||||||
|
keyBindings,
|
||||||
}) {
|
}) {
|
||||||
this.element = element
|
this.element = element
|
||||||
this.path = path
|
this.path = path
|
||||||
@ -73,20 +66,20 @@ export class HeynoteEditor {
|
|||||||
this.notesStore = useHeynoteStore()
|
this.notesStore = useHeynoteStore()
|
||||||
this.errorStore = useErrorStore()
|
this.errorStore = useErrorStore()
|
||||||
this.name = ""
|
this.name = ""
|
||||||
|
this.selectionMarkMode = false
|
||||||
|
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: "",
|
doc: "",
|
||||||
extensions: [
|
extensions: [
|
||||||
this.keymapCompartment.of(getKeymapExtensions(this, keymap)),
|
this.keymapCompartment.of(getKeymapExtensions(this, keymap, keyBindings)),
|
||||||
heynoteCopyCut(this),
|
heynoteCopyCut(this),
|
||||||
|
|
||||||
//minimalSetup,
|
//minimalSetup,
|
||||||
this.lineNumberCompartment.of(showLineNumberGutter ? [lineNumbers(), blockLineNumbers] : []),
|
this.lineNumberCompartment.of(showLineNumberGutter ? [lineNumbers(), blockLineNumbers] : []),
|
||||||
customSetup,
|
customSetup,
|
||||||
this.foldGutterCompartment.of(showFoldGutter ? [foldGutter()] : []),
|
this.foldGutterCompartment.of(showFoldGutter ? [foldGutter()] : []),
|
||||||
|
this.closeBracketsCompartment.of(bracketClosing ? [getCloseBracketsExtensions()] : []),
|
||||||
this.closeBracketsCompartment.of(bracketClosing ? [closeBrackets()] : []),
|
|
||||||
|
|
||||||
this.readOnlyCompartment.of([]),
|
this.readOnlyCompartment.of([]),
|
||||||
|
|
||||||
@ -111,8 +104,12 @@ export class HeynoteEditor {
|
|||||||
|
|
||||||
autoSaveContent(this, AUTO_SAVE_INTERVAL),
|
autoSaveContent(this, AUTO_SAVE_INTERVAL),
|
||||||
|
|
||||||
|
// Markdown extensions, we need to add markdownKeymap manually with the highest precedence
|
||||||
|
// so that it takes precedence over the default keymap
|
||||||
todoCheckboxPlugin,
|
todoCheckboxPlugin,
|
||||||
markdown(),
|
markdown({addKeymap: false}),
|
||||||
|
Prec.highest(cmKeymap.of(markdownKeymap)),
|
||||||
|
|
||||||
links,
|
links,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -137,6 +134,11 @@ export class HeynoteEditor {
|
|||||||
if (focus) {
|
if (focus) {
|
||||||
this.view.focus()
|
this.view.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trigger setFont once the fonts has loaded
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
this.setFont(fontFamily, fontSize)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
@ -260,6 +262,7 @@ export class HeynoteEditor {
|
|||||||
setFont(fontFamily, fontSize) {
|
setFont(fontFamily, fontSize) {
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
effects: this.fontTheme.reconfigure(getFontTheme(fontFamily, fontSize)),
|
effects: this.fontTheme.reconfigure(getFontTheme(fontFamily, fontSize)),
|
||||||
|
annotations: [heynoteEvent.of(SET_FONT), Transaction.addToHistory.of(false)],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,11 +272,11 @@ export class HeynoteEditor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeymap(keymap, emacsMetaKey) {
|
setKeymap(keymap, emacsMetaKey, keyBindings) {
|
||||||
this.deselectOnCopy = keymap === "emacs"
|
this.deselectOnCopy = keymap === "emacs"
|
||||||
this.emacsMetaKey = emacsMetaKey
|
this.emacsMetaKey = emacsMetaKey
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
effects: this.keymapCompartment.reconfigure(getKeymapExtensions(this, keymap)),
|
effects: this.keymapCompartment.reconfigure(getKeymapExtensions(this, keymap, keyBindings)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +288,10 @@ export class HeynoteEditor {
|
|||||||
this.notesStore.openBufferSelector()
|
this.notesStore.openBufferSelector()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openCommandPalette() {
|
||||||
|
this.notesStore.openCommandPalette()
|
||||||
|
}
|
||||||
|
|
||||||
openCreateBuffer(createMode) {
|
openCreateBuffer(createMode) {
|
||||||
this.notesStore.openCreateBuffer(createMode)
|
this.notesStore.openCreateBuffer(createMode)
|
||||||
}
|
}
|
||||||
@ -367,7 +374,7 @@ export class HeynoteEditor {
|
|||||||
|
|
||||||
setBracketClosing(value) {
|
setBracketClosing(value) {
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
effects: this.closeBracketsCompartment.reconfigure(value ? [closeBrackets()] : []),
|
effects: this.closeBracketsCompartment.reconfigure(value ? [getCloseBracketsExtensions()] : []),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,6 +432,15 @@ export class HeynoteEditor {
|
|||||||
effects: this.indentUnitCompartment.reconfigure(indentUnit.of(" ".repeat(tabSize)))
|
effects: this.indentUnitCompartment.reconfigure(indentUnit.of(" ".repeat(tabSize)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executeCommand(command) {
|
||||||
|
const cmd = HEYNOTE_COMMANDS[command]
|
||||||
|
if (!cmd) {
|
||||||
|
console.error(`Command not found: ${command}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.run(this)(this.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
import { Direction} from "@codemirror/view"
|
|
||||||
import { EditorSelection, EditorState, Prec } from "@codemirror/state"
|
|
||||||
import {
|
|
||||||
undo, redo,
|
|
||||||
cursorGroupLeft, cursorGroupRight, selectGroupLeft, selectGroupRight,
|
|
||||||
simplifySelection,
|
|
||||||
deleteCharForward, deleteCharBackward, deleteToLineEnd,
|
|
||||||
splitLine,
|
|
||||||
transposeChars,
|
|
||||||
cursorPageDown,
|
|
||||||
cursorCharLeft, selectCharLeft,
|
|
||||||
cursorCharRight, selectCharRight,
|
|
||||||
cursorLineUp, selectLineUp,
|
|
||||||
cursorLineDown, selectLineDown,
|
|
||||||
cursorLineStart, selectLineStart,
|
|
||||||
cursorLineEnd, selectLineEnd,
|
|
||||||
} from "@codemirror/commands"
|
|
||||||
import { heynoteKeymap, keymapFromSpec } from "./keymap.js"
|
|
||||||
|
|
||||||
import {
|
|
||||||
gotoPreviousBlock, gotoNextBlock,
|
|
||||||
selectNextBlock, selectPreviousBlock,
|
|
||||||
gotoPreviousParagraph, gotoNextParagraph,
|
|
||||||
selectNextParagraph, selectPreviousParagraph,
|
|
||||||
selectAll,
|
|
||||||
} from "./block/commands.js"
|
|
||||||
import { pasteCommand, copyCommand, cutCommand } from "./copy-paste.js"
|
|
||||||
|
|
||||||
|
|
||||||
// if set to true, all keybindings for moving around is changed to their corresponding select commands
|
|
||||||
let emacsMarkMode = false
|
|
||||||
|
|
||||||
export function setEmacsMarkMode(value) {
|
|
||||||
emacsMarkMode = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a command that will conditionally execute either the default command or the mark mode command
|
|
||||||
*
|
|
||||||
* @param defaultCmd Default command to execute
|
|
||||||
* @param {*} markModeCmd Command to execute if mark mode is active
|
|
||||||
*/
|
|
||||||
function emacsMoveCommand(defaultCmd, markModeCmd) {
|
|
||||||
return (view) => emacsMarkMode ? markModeCmd(view) : defaultCmd(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* C-g command that exits mark mode and simplifies selection
|
|
||||||
*/
|
|
||||||
function emacsCancel(view) {
|
|
||||||
simplifySelection(view)
|
|
||||||
setEmacsMarkMode(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exit mark mode before executing selectAll command
|
|
||||||
*/
|
|
||||||
function emacsSelectAll(view) {
|
|
||||||
setEmacsMarkMode(false)
|
|
||||||
return selectAll(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function emacsMetaKeyCommand(key, editor, command) {
|
|
||||||
const handler = (view, event) => {
|
|
||||||
if (editor.emacsMetaKey === "meta" && event.metaKey || editor.emacsMetaKey === "alt" && event.altKey) {
|
|
||||||
event.preventDefault()
|
|
||||||
return command(view)
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{key, run:handler, preventDefault:false},
|
|
||||||
{key:key.replace("Meta", "Alt"), run:handler, preventDefault:false},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emacsKeymap(editor) {
|
|
||||||
return [
|
|
||||||
heynoteKeymap(editor),
|
|
||||||
Prec.highest(keymapFromSpec([
|
|
||||||
["Ctrl-Shift--", undo],
|
|
||||||
["Ctrl-.", redo],
|
|
||||||
["Ctrl-g", emacsCancel],
|
|
||||||
["ArrowLeft", emacsMoveCommand(cursorCharLeft, selectCharLeft)],
|
|
||||||
["ArrowRight", emacsMoveCommand(cursorCharRight, selectCharRight)],
|
|
||||||
["ArrowUp", emacsMoveCommand(cursorLineUp, selectLineUp)],
|
|
||||||
["ArrowDown", emacsMoveCommand(cursorLineDown, selectLineDown)],
|
|
||||||
{key: "Ctrl-ArrowLeft", run: emacsMoveCommand(cursorGroupLeft, selectGroupLeft), shift: selectGroupLeft},
|
|
||||||
{key: "Ctrl-ArrowRight", run: emacsMoveCommand(cursorGroupRight, selectGroupRight), shift: selectGroupRight},
|
|
||||||
|
|
||||||
["Ctrl-d", deleteCharForward],
|
|
||||||
["Ctrl-h", deleteCharBackward],
|
|
||||||
["Ctrl-k", deleteToLineEnd],
|
|
||||||
["Ctrl-o", splitLine],
|
|
||||||
["Ctrl-t", transposeChars],
|
|
||||||
["Ctrl-v", cursorPageDown],
|
|
||||||
|
|
||||||
["Ctrl-y", pasteCommand],
|
|
||||||
["Ctrl-w", cutCommand(editor)],
|
|
||||||
...emacsMetaKeyCommand("Meta-w", editor, copyCommand(editor)),
|
|
||||||
|
|
||||||
{ key: "Ctrl-b", run: emacsMoveCommand(cursorCharLeft, selectCharLeft), shift: selectCharLeft },
|
|
||||||
{ key: "Ctrl-f", run: emacsMoveCommand(cursorCharRight, selectCharRight), shift: selectCharRight },
|
|
||||||
{ key: "Ctrl-a", run: emacsMoveCommand(cursorLineStart, selectLineStart), shift: selectLineStart },
|
|
||||||
{ key: "Ctrl-e", run: emacsMoveCommand(cursorLineEnd, selectLineEnd), shift: selectLineEnd },
|
|
||||||
])),
|
|
||||||
|
|
||||||
Prec.highest(keymapFromSpec([
|
|
||||||
["Ctrl-Space", (view) => { emacsMarkMode = !emacsMarkMode }],
|
|
||||||
["Mod-a", emacsSelectAll],
|
|
||||||
{key:"Mod-ArrowUp", run:emacsMoveCommand(gotoPreviousBlock, selectPreviousBlock), shift:selectPreviousBlock},
|
|
||||||
{key:"Mod-ArrowDown", run:emacsMoveCommand(gotoNextBlock, selectNextBlock), shift:selectNextBlock},
|
|
||||||
{key:"Ctrl-ArrowUp", run:emacsMoveCommand(gotoPreviousParagraph, selectPreviousParagraph), shift:selectPreviousParagraph},
|
|
||||||
{key:"Ctrl-ArrowDown", run:emacsMoveCommand(gotoNextParagraph, selectNextParagraph), shift:selectNextParagraph},
|
|
||||||
])),
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,75 +1,165 @@
|
|||||||
import { keymap } from "@codemirror/view"
|
import { keymap } from "@codemirror/view"
|
||||||
//import { EditorSelection, EditorState } from "@codemirror/state"
|
import { Prec } from "@codemirror/state"
|
||||||
import {
|
|
||||||
indentLess, indentMore, redo,
|
|
||||||
} from "@codemirror/commands"
|
|
||||||
|
|
||||||
import {
|
import { HEYNOTE_COMMANDS } from "./commands.js"
|
||||||
insertNewBlockAtCursor,
|
|
||||||
addNewBlockBeforeCurrent, addNewBlockAfterCurrent,
|
|
||||||
addNewBlockBeforeFirst, addNewBlockAfterLast,
|
|
||||||
moveLineUp, moveLineDown,
|
|
||||||
selectAll,
|
|
||||||
gotoPreviousBlock, gotoNextBlock,
|
|
||||||
selectNextBlock, selectPreviousBlock,
|
|
||||||
gotoPreviousParagraph, gotoNextParagraph,
|
|
||||||
selectNextParagraph, selectPreviousParagraph,
|
|
||||||
newCursorBelow, newCursorAbove,
|
|
||||||
deleteBlock,
|
|
||||||
} from "./block/commands.js"
|
|
||||||
import { pasteCommand, copyCommand, cutCommand } from "./copy-paste.js"
|
|
||||||
|
|
||||||
import { formatBlockContent } from "./block/format-code.js"
|
const cmd = (key, command) => ({key, command})
|
||||||
import { deleteLine } from "./block/delete-line.js"
|
const cmdShift = (key, command, shiftCommand) => {
|
||||||
|
return [
|
||||||
|
cmd(key, command),
|
||||||
|
cmd(`Shift-${key}`, shiftCommand),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMac = window.heynote.platform.isMac
|
||||||
|
const isLinux = window.heynote.platform.isLinux
|
||||||
|
const isWindows = window.heynote.platform.isWindows
|
||||||
|
|
||||||
|
export const DEFAULT_KEYMAP = [
|
||||||
|
cmd("Enter", "insertNewlineAndIndent"),
|
||||||
|
|
||||||
|
cmd("Mod-a", "selectAll"),
|
||||||
|
cmd("Mod-Enter", "addNewBlockAfterCurrent"),
|
||||||
|
cmd("Mod-Shift-Enter", "addNewBlockAfterLast"),
|
||||||
|
cmd("Alt-Enter", "addNewBlockBeforeCurrent"),
|
||||||
|
cmd("Alt-Shift-Enter", "addNewBlockBeforeFirst"),
|
||||||
|
cmd("Mod-Alt-Enter", "insertNewBlockAtCursor"),
|
||||||
|
...cmdShift("ArrowLeft", "cursorCharLeft", "selectCharLeft"),
|
||||||
|
...cmdShift("ArrowRight", "cursorCharRight", "selectCharRight"),
|
||||||
|
...cmdShift("ArrowUp", "cursorLineUp", "selectLineUp"),
|
||||||
|
...cmdShift("ArrowDown", "cursorLineDown", "selectLineDown"),
|
||||||
|
...cmdShift("Ctrl-ArrowLeft", "cursorGroupLeft", "selectGroupLeft"),
|
||||||
|
...cmdShift("Ctrl-ArrowRight", "cursorGroupRight", "selectGroupRight"),
|
||||||
|
...cmdShift("Alt-ArrowLeft", "cursorGroupLeft", "selectGroupLeft"),
|
||||||
|
...cmdShift("Alt-ArrowRight", "cursorGroupRight", "selectGroupRight"),
|
||||||
|
...cmdShift("Mod-ArrowUp", "cursorPreviousBlock", "selectPreviousBlock"),
|
||||||
|
...cmdShift("Mod-ArrowDown", "cursorNextBlock", "selectNextBlock"),
|
||||||
|
...cmdShift("Ctrl-ArrowUp", "cursorPreviousParagraph", "selectPreviousParagraph"),
|
||||||
|
...cmdShift("Ctrl-ArrowDown", "cursorNextParagraph", "selectNextParagraph"),
|
||||||
|
...cmdShift("PageUp", "cursorPageUp", "selectPageUp"),
|
||||||
|
...cmdShift("PageDown", "cursorPageDown", "selectPageDown"),
|
||||||
|
...cmdShift("Home", "cursorLineBoundaryBackward", "selectLineBoundaryBackward"),
|
||||||
|
...cmdShift("End", "cursorLineBoundaryForward", "selectLineBoundaryForward"),
|
||||||
|
cmd("Backspace", "deleteCharBackward"),
|
||||||
|
cmd("Delete", "deleteCharForward"),
|
||||||
|
cmd("Escape", "simplifySelection"),
|
||||||
|
cmd("Ctrl-Backspace", "deleteGroupBackward"),
|
||||||
|
cmd("Ctrl-Delete", "deleteGroupForward"),
|
||||||
|
...(isMac ? [
|
||||||
|
cmd("Alt-Backspace", "deleteGroupBackward"),
|
||||||
|
cmd("Alt-Delete", "deleteGroupForward"),
|
||||||
|
cmd("Mod-Backspace", "deleteLineBoundaryBackward"),
|
||||||
|
cmd("Mod-Delete", "deleteLineBoundaryForward"),
|
||||||
|
] : []),
|
||||||
|
|
||||||
|
cmd("Alt-ArrowUp", "moveLineUp"),
|
||||||
|
cmd("Alt-ArrowDown", "moveLineDown"),
|
||||||
|
cmd("Mod-Shift-k", "deleteLine"),
|
||||||
|
cmd("Mod-Alt-ArrowDown", "newCursorBelow"),
|
||||||
|
cmd("Mod-Alt-ArrowUp", "newCursorAbove"),
|
||||||
|
cmd("Mod-Shift-d", "deleteBlock"),
|
||||||
|
cmd("Mod-d", "selectNextOccurrence"),
|
||||||
|
cmd(isMac ? "Cmd-Alt-[" : "Ctrl-Shift-[", "foldCode"),
|
||||||
|
cmd(isMac ? "Cmd-Alt-]" : "Ctrl-Shift-]", "unfoldCode"),
|
||||||
|
|
||||||
|
cmd("Mod-c", "copy"),
|
||||||
|
cmd("Mod-v", "paste"),
|
||||||
|
cmd("Mod-x", "cut"),
|
||||||
|
cmd("Mod-z", "undo"),
|
||||||
|
cmd("Mod-Shift-z", "redo"),
|
||||||
|
...(isWindows || isLinux ? [
|
||||||
|
cmd("Mod-y", "redo"),
|
||||||
|
] : []),
|
||||||
|
|
||||||
|
cmd("Tab", "indentMore"),
|
||||||
|
cmd("Shift-Tab", "indentLess"),
|
||||||
|
//cmd("Alt-ArrowLeft", "cursorSubwordBackward"),
|
||||||
|
//cmd("Alt-ArrowRight", "cursorSubwordForward"),
|
||||||
|
|
||||||
|
cmd("Mod-l", "openLanguageSelector"),
|
||||||
|
cmd("Mod-p", "openBufferSelector"),
|
||||||
|
cmd("Mod-Shift-p", "openCommandPalette"),
|
||||||
|
cmd("Mod-s", "openMoveToBuffer"),
|
||||||
|
cmd("Mod-n", "openCreateNewBuffer"),
|
||||||
|
|
||||||
|
cmd("Alt-Shift-f", "formatBlockContent"),
|
||||||
|
|
||||||
|
// search
|
||||||
|
//cmd("Mod-f", "openSearchPanel"),
|
||||||
|
//cmd("F3", "findNext"),
|
||||||
|
//cmd("Mod-g", "findNext"),
|
||||||
|
//cmd("Shift-F3", "findPrevious"),
|
||||||
|
//cmd("Shift-Mod-g", "findPrevious"),
|
||||||
|
//cmd("Mod-Alt-g", "gotoLine"),
|
||||||
|
//cmd("Mod-d", "selectNextOccurrence"),
|
||||||
|
/*
|
||||||
|
- Mod-f: [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel)
|
||||||
|
- F3, Mod-g: [`findNext`](https://codemirror.net/6/docs/ref/#search.findNext)
|
||||||
|
- Shift-F3, Shift-Mod-g: [`findPrevious`](https://codemirror.net/6/docs/ref/#search.findPrevious)
|
||||||
|
- Mod-Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine)
|
||||||
|
- Mod-d: [`selectNextOccurrence`](https://codemirror.net/6/docs/ref/#search.selectNextOccurrence)
|
||||||
|
*/
|
||||||
|
]
|
||||||
|
|
||||||
|
export const EMACS_KEYMAP = [
|
||||||
|
cmd("Ctrl-w", "cut"),
|
||||||
|
cmd("Ctrl-y", "paste"),
|
||||||
|
cmd("EmacsMeta-w", "copy"),
|
||||||
|
cmd("Ctrl-Space", "toggleSelectionMarkMode"),
|
||||||
|
cmd("Ctrl-g", "selectionMarkModeCancel"),
|
||||||
|
cmd("Escape", "selectionMarkModeCancel"),
|
||||||
|
cmd("Ctrl-o", "splitLine"),
|
||||||
|
cmd("Ctrl-d", "deleteCharForward"),
|
||||||
|
cmd("Ctrl-h", "deleteCharBackward"),
|
||||||
|
cmd("Ctrl-k", "deleteToLineEnd"),
|
||||||
|
cmd("Ctrl-t", "transposeChars"),
|
||||||
|
cmd("Ctrl-Shift--", "undo"),
|
||||||
|
cmd("Ctrl-.", "redo"),
|
||||||
|
...cmdShift("Ctrl-v", "cursorPageDown", "selectPageDown"),
|
||||||
|
...cmdShift("Ctrl-b", "cursorCharLeft", "selectCharLeft"),
|
||||||
|
...cmdShift("Ctrl-f", "cursorCharRight", "selectCharRight"),
|
||||||
|
...cmdShift("Ctrl-a", "cursorLineStart", "selectLineStart"),
|
||||||
|
...cmdShift("Ctrl-e", "cursorLineEnd", "selectLineEnd"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
export function keymapFromSpec(specs) {
|
function keymapFromSpec(specs, editor) {
|
||||||
return keymap.of(specs.map((spec) => {
|
return keymap.of(specs.map((spec) => {
|
||||||
if (spec.run) {
|
let key = spec.key
|
||||||
if ("preventDefault" in spec) {
|
if (key.indexOf("EmacsMeta") != -1) {
|
||||||
return spec
|
key = key.replace("EmacsMeta", editor.emacsMetaKey === "alt" ? "Alt" : "Meta")
|
||||||
} else {
|
|
||||||
return {...spec, preventDefault: true}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const [key, run] = spec
|
|
||||||
return {
|
return {
|
||||||
key,
|
key: key,
|
||||||
run,
|
//preventDefault: true,
|
||||||
preventDefault: true,
|
preventDefault: false,
|
||||||
|
run: (view) => {
|
||||||
|
//console.log("run()", spec.key, spec.command)
|
||||||
|
const command = HEYNOTE_COMMANDS[spec.command]
|
||||||
|
if (!command) {
|
||||||
|
console.error(`Command not found: ${spec.command} (${spec.key})`)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return command.run(editor)(view)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function heynoteKeymap(editor) {
|
|
||||||
return keymapFromSpec([
|
export function heynoteKeymap(editor, keymap, userKeymap) {
|
||||||
["Mod-c", copyCommand(editor)],
|
return [
|
||||||
["Mod-v", pasteCommand],
|
keymapFromSpec([
|
||||||
["Mod-x", cutCommand(editor)],
|
...userKeymap,
|
||||||
["Tab", indentMore],
|
...keymap,
|
||||||
["Shift-Tab", indentLess],
|
], editor),
|
||||||
["Alt-Shift-Enter", addNewBlockBeforeFirst(editor)],
|
]
|
||||||
["Mod-Shift-Enter", addNewBlockAfterLast(editor)],
|
}
|
||||||
["Alt-Enter", addNewBlockBeforeCurrent(editor)],
|
|
||||||
["Mod-Enter", addNewBlockAfterCurrent(editor)],
|
export function getKeymapExtensions(editor, keymap, keyBindings) {
|
||||||
["Mod-Alt-Enter", insertNewBlockAtCursor(editor)],
|
return heynoteKeymap(
|
||||||
["Mod-a", selectAll],
|
editor,
|
||||||
["Alt-ArrowUp", moveLineUp],
|
keymap === "emacs" ? EMACS_KEYMAP.concat(DEFAULT_KEYMAP) : DEFAULT_KEYMAP,
|
||||||
["Alt-ArrowDown", moveLineDown],
|
keyBindings || [],
|
||||||
["Mod-l", () => editor.openLanguageSelector()],
|
)
|
||||||
["Mod-p", () => editor.openBufferSelector()],
|
|
||||||
["Mod-s", () => editor.openMoveToBufferSelector()],
|
|
||||||
["Mod-n", () => editor.openCreateBuffer("new")],
|
|
||||||
["Mod-Shift-d", deleteBlock(editor)],
|
|
||||||
["Alt-Shift-f", formatBlockContent],
|
|
||||||
["Mod-Alt-ArrowDown", newCursorBelow],
|
|
||||||
["Mod-Alt-ArrowUp", newCursorAbove],
|
|
||||||
["Mod-Shift-k", deleteLine],
|
|
||||||
["Mod-Shift-z", redo],
|
|
||||||
{key:"Mod-ArrowUp", run:gotoPreviousBlock, shift:selectPreviousBlock},
|
|
||||||
{key:"Mod-ArrowDown", run:gotoNextBlock, shift:selectNextBlock},
|
|
||||||
{key:"Ctrl-ArrowUp", run:gotoPreviousParagraph, shift:selectPreviousParagraph},
|
|
||||||
{key:"Ctrl-ArrowDown", run:gotoNextParagraph, shift:selectNextParagraph},
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
39
src/editor/mark-mode.js
Normal file
39
src/editor/mark-mode.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
simplifySelection,
|
||||||
|
} from "@codemirror/commands"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a command that moves the cursor and a command that marks the selection, and returns a new command that
|
||||||
|
* will run the mark command if we're in Emacs mark mode, or the move command otherwise.
|
||||||
|
*/
|
||||||
|
export function markModeMoveCommand(defaultCmd, markModeCmd) {
|
||||||
|
return (editor) => {
|
||||||
|
if (editor.selectionMarkMode) {
|
||||||
|
return (view) => {
|
||||||
|
markModeCmd(view)
|
||||||
|
// we need to return true here instead of returning what the default command returns, since the default
|
||||||
|
// codemirror select commands will return false if the selection doesn't change, which in turn will
|
||||||
|
// make the default *move* command run which will kill the selection if we're in mark mode
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (view) => defaultCmd(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function toggleSelectionMarkMode(editor) {
|
||||||
|
return (view) => {
|
||||||
|
editor.selectionMarkMode = !editor.selectionMarkMode
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectionMarkModeCancel(editor) {
|
||||||
|
return (view) => {
|
||||||
|
simplifySelection(view)
|
||||||
|
editor.selectionMarkMode = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -68,11 +68,12 @@ const customSetup = /*@__PURE__*/(() => [
|
|||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
scrollPastEnd(),
|
scrollPastEnd(),
|
||||||
keymap.of([
|
keymap.of([
|
||||||
...closeBracketsKeymap,
|
//...closeBracketsKeymap,
|
||||||
...defaultKeymap,
|
//...defaultKeymap,
|
||||||
...searchKeymap,
|
...searchKeymap,
|
||||||
...historyKeymap,
|
//...historyKeymap,
|
||||||
...foldKeymap,
|
//...foldKeymap,
|
||||||
|
|
||||||
//...completionKeymap,
|
//...completionKeymap,
|
||||||
//...lintKeymap
|
//...lintKeymap
|
||||||
])
|
])
|
||||||
|
@ -1,11 +1,39 @@
|
|||||||
import { EditorView } from "@codemirror/view"
|
import { EditorView } from "@codemirror/view"
|
||||||
|
import { Facet } from "@codemirror/state"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given font family is monospace by drawing test characters on a canvas
|
||||||
|
*/
|
||||||
|
function isMonospace(fontFamily) {
|
||||||
|
const testCharacters = ['i', 'W', 'm', ' ']
|
||||||
|
const testSize = '72px'
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
context.font = `${testSize} ${fontFamily}`
|
||||||
|
|
||||||
|
const widths = testCharacters.map(char => context.measureText(char).width)
|
||||||
|
return widths.every(width => width === widths[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const isMonospaceFont = Facet.define({
|
||||||
|
combine: values => values.length ? values[0] : true,
|
||||||
|
})
|
||||||
|
|
||||||
export function getFontTheme(fontFamily, fontSize) {
|
export function getFontTheme(fontFamily, fontSize) {
|
||||||
fontSize = fontSize || window.heynote.defaultFontSize
|
fontSize = fontSize || window.heynote.defaultFontSize
|
||||||
return EditorView.theme({
|
const computedFontFamily = fontFamily || window.heynote.defaultFontFamily
|
||||||
|
return [
|
||||||
|
EditorView.theme({
|
||||||
'.cm-scroller': {
|
'.cm-scroller': {
|
||||||
fontFamily: fontFamily || window.heynote.defaultFontFamily,
|
fontFamily: computedFontFamily,
|
||||||
fontSize: (fontSize) + "px",
|
fontSize: (fontSize) + "px",
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
|
// in order to avoid a short flicker when the program is loaded with the default font (Hack),
|
||||||
|
// we hardcode Hack to be monospace
|
||||||
|
isMonospaceFont.of(computedFontFamily === "Hack" ? true : isMonospace(computedFontFamily)),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -3,25 +3,45 @@ import { syntaxTree, ensureSyntaxTree } from "@codemirror/language"
|
|||||||
import { WidgetType } from "@codemirror/view"
|
import { WidgetType } from "@codemirror/view"
|
||||||
import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view"
|
import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view"
|
||||||
|
|
||||||
|
import { isMonospaceFont } from "./theme/font-theme"
|
||||||
|
import { transactionsHasAnnotation, SET_FONT } from "./annotation"
|
||||||
|
|
||||||
|
|
||||||
class CheckboxWidget extends WidgetType {
|
class CheckboxWidget extends WidgetType {
|
||||||
constructor(readonly checked: boolean) { super() }
|
constructor(readonly checked: boolean, readonly monospace: boolean) { super() }
|
||||||
|
|
||||||
eq(other: CheckboxWidget) { return other.checked == this.checked }
|
eq(other: CheckboxWidget) { return other.checked == this.checked && other.monospace == this.monospace }
|
||||||
|
|
||||||
toDOM() {
|
toDOM() {
|
||||||
let wrap = document.createElement("span")
|
let wrap = document.createElement("span")
|
||||||
wrap.setAttribute("aria-hidden", "true")
|
wrap.setAttribute("aria-hidden", "true")
|
||||||
wrap.className = "cm-taskmarker-toggle"
|
wrap.className = "cm-taskmarker-toggle"
|
||||||
wrap.style.position = "relative"
|
|
||||||
// Three spaces since it's the same width as [ ] and [x]
|
let box = document.createElement("input")
|
||||||
wrap.appendChild(document.createTextNode(" "))
|
|
||||||
let box = wrap.appendChild(document.createElement("input"))
|
|
||||||
box.type = "checkbox"
|
box.type = "checkbox"
|
||||||
box.checked = this.checked
|
box.checked = this.checked
|
||||||
|
box.tabIndex = -1
|
||||||
|
box.style.margin = "0"
|
||||||
|
box.style.padding = "0"
|
||||||
|
|
||||||
|
if (this.monospace) {
|
||||||
|
// if the font is monospaced, we'll set the content of the wrapper to " " and the
|
||||||
|
// position of the checkbox to absolute, since three spaces will be the same width
|
||||||
|
// as "[ ]" and "[x]" so that characters on different lines will line up
|
||||||
|
wrap.appendChild(document.createTextNode(" "))
|
||||||
|
wrap.style.position = "relative"
|
||||||
box.style.position = "absolute"
|
box.style.position = "absolute"
|
||||||
box.style.top = "-3px"
|
box.style.top = "0"
|
||||||
box.style.left = "0"
|
box.style.left = "0.25em"
|
||||||
|
box.style.width = "1.1em"
|
||||||
|
box.style.height = "1.1em"
|
||||||
|
} else {
|
||||||
|
// if the font isn't monospaced, we'll let the checkbox take up as much space as needed
|
||||||
|
box.style.position = "relative"
|
||||||
|
box.style.top = "0.1em"
|
||||||
|
box.style.marginRight = "0.5em"
|
||||||
|
}
|
||||||
|
wrap.appendChild(box)
|
||||||
return wrap
|
return wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +72,7 @@ function checkboxes(view: EditorView) {
|
|||||||
if (view.state.doc.sliceString(nodeRef.to, nodeRef.to+1) === " ") {
|
if (view.state.doc.sliceString(nodeRef.to, nodeRef.to+1) === " ") {
|
||||||
let isChecked = view.state.doc.sliceString(nodeRef.from, nodeRef.to).toLowerCase() === "[x]"
|
let isChecked = view.state.doc.sliceString(nodeRef.from, nodeRef.to).toLowerCase() === "[x]"
|
||||||
let deco = Decoration.replace({
|
let deco = Decoration.replace({
|
||||||
widget: new CheckboxWidget(isChecked),
|
widget: new CheckboxWidget(isChecked, view.state.facet(isMonospaceFont)),
|
||||||
inclusive: false,
|
inclusive: false,
|
||||||
})
|
})
|
||||||
widgets.push(deco.range(nodeRef.from, nodeRef.to))
|
widgets.push(deco.range(nodeRef.from, nodeRef.to))
|
||||||
@ -92,9 +112,10 @@ export const todoCheckboxPlugin = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
if (update.docChanged || update.viewportChanged)
|
if (update.docChanged || update.viewportChanged || transactionsHasAnnotation(update.transactions, SET_FONT)) {
|
||||||
this.decorations = checkboxes(update.view)
|
this.decorations = checkboxes(update.view)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
decorations: v => v.decorations,
|
decorations: v => v.decorations,
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import './css/application.sass'
|
import './css/application.sass'
|
||||||
|
import '../assets/font/open-sans/open-sans.css'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
|
|
||||||
import App from './components/App.vue'
|
import App from './components/App.vue'
|
||||||
import { loadCurrencies } from './currency'
|
import { loadCurrencies } from './currency'
|
||||||
@ -13,6 +15,7 @@ const pinia = createPinia()
|
|||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
app.use(PrimeVue)
|
||||||
app.mount('#app').$nextTick(() => {
|
app.mount('#app').$nextTick(() => {
|
||||||
// hide loading screen
|
// hide loading screen
|
||||||
postMessage({ payload: 'removeLoading' }, '*')
|
postMessage({ payload: 'removeLoading' }, '*')
|
||||||
|
@ -39,6 +39,7 @@ export const useEditorCacheStore = defineStore("editorCache", {
|
|||||||
tabSize: settingsStore.settings.tabSize,
|
tabSize: settingsStore.settings.tabSize,
|
||||||
defaultBlockToken: settingsStore.settings.defaultBlockLanguage,
|
defaultBlockToken: settingsStore.settings.defaultBlockLanguage,
|
||||||
defaultBlockAutoDetect: settingsStore.settings.defaultBlockLanguageAutoDetect,
|
defaultBlockAutoDetect: settingsStore.settings.defaultBlockLanguageAutoDetect,
|
||||||
|
keyBindings: settingsStore.settings.keyBindings,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorStore.addError("Error! " + e.message)
|
errorStore.addError("Error! " + e.message)
|
||||||
@ -123,7 +124,8 @@ export const useEditorCacheStore = defineStore("editorCache", {
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case "keymap":
|
case "keymap":
|
||||||
case "emacsMetaKey":
|
case "emacsMetaKey":
|
||||||
editor.setKeymap(newSettings.keymap, newSettings.emacsMetaKey)
|
case "keyBindings":
|
||||||
|
editor.setKeymap(newSettings.keymap, newSettings.emacsMetaKey, newSettings.keyBindings)
|
||||||
break
|
break
|
||||||
case "showLineNumberGutter":
|
case "showLineNumberGutter":
|
||||||
editor.setLineNumberGutter(newSettings.showLineNumberGutter)
|
editor.setLineNumberGutter(newSettings.showLineNumberGutter)
|
||||||
@ -168,6 +170,9 @@ export const useEditorCacheStore = defineStore("editorCache", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.document.removeEventListener("currenciesLoaded", this.onCurrenciesLoaded)
|
window.document.removeEventListener("currenciesLoaded", this.onCurrenciesLoaded)
|
||||||
|
|
||||||
|
this.editorCache.lru = []
|
||||||
|
this.editorCache.cache = {}
|
||||||
},
|
},
|
||||||
|
|
||||||
moveCurrentBlockToOtherEditor(targetPath) {
|
moveCurrentBlockToOtherEditor(targetPath) {
|
||||||
|
@ -28,6 +28,7 @@ export const useHeynoteStore = defineStore("heynote", {
|
|||||||
showCreateBuffer: false,
|
showCreateBuffer: false,
|
||||||
showEditBuffer: false,
|
showEditBuffer: false,
|
||||||
showMoveToBufferSelector: false,
|
showMoveToBufferSelector: false,
|
||||||
|
showCommandPalette: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@ -58,6 +59,11 @@ export const useHeynoteStore = defineStore("heynote", {
|
|||||||
openBufferSelector() {
|
openBufferSelector() {
|
||||||
this.closeDialog()
|
this.closeDialog()
|
||||||
this.showBufferSelector = true
|
this.showBufferSelector = true
|
||||||
|
|
||||||
|
},
|
||||||
|
openCommandPalette() {
|
||||||
|
this.closeDialog()
|
||||||
|
this.showCommandPalette = true
|
||||||
},
|
},
|
||||||
openMoveToBufferSelector() {
|
openMoveToBufferSelector() {
|
||||||
this.closeDialog()
|
this.closeDialog()
|
||||||
@ -78,10 +84,12 @@ export const useHeynoteStore = defineStore("heynote", {
|
|||||||
this.showLanguageSelector = false
|
this.showLanguageSelector = false
|
||||||
this.showEditBuffer = false
|
this.showEditBuffer = false
|
||||||
this.showMoveToBufferSelector = false
|
this.showMoveToBufferSelector = false
|
||||||
|
this.showCommandPalette = false
|
||||||
},
|
},
|
||||||
|
|
||||||
closeBufferSelector() {
|
closeBufferSelector() {
|
||||||
this.showBufferSelector = false
|
this.showBufferSelector = false
|
||||||
|
this.showCommandPalette = false
|
||||||
},
|
},
|
||||||
|
|
||||||
closeMoveToBufferSelector() {
|
closeMoveToBufferSelector() {
|
||||||
@ -96,6 +104,13 @@ export const useHeynoteStore = defineStore("heynote", {
|
|||||||
this.showEditBuffer = true
|
this.showEditBuffer = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
executeCommand(command) {
|
||||||
|
if (this.currentEditor) {
|
||||||
|
toRaw(this.currentEditor).executeCommand(command)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new note file at `path` with name `name` from the current block of the current open editor,
|
* Create a new note file at `path` with name `name` from the current block of the current open editor,
|
||||||
* and switch to it
|
* and switch to it
|
||||||
|
@ -22,7 +22,7 @@ test("test default font is Hack", async ({ page }) => {
|
|||||||
})).toBeLessThan(20)
|
})).toBeLessThan(20)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("test custom font", async ({ page, browserName }) => {
|
test("test custom font", async ({ page }) => {
|
||||||
// monkey patch window.queryLocalFonts because it's not available in Playwright
|
// monkey patch window.queryLocalFonts because it's not available in Playwright
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
window.queryLocalFonts = async () => {
|
window.queryLocalFonts = async () => {
|
||||||
@ -64,3 +64,55 @@ test("test custom font", async ({ page, browserName }) => {
|
|||||||
return el.clientHeight
|
return el.clientHeight
|
||||||
})).toBeGreaterThan(20)
|
})).toBeGreaterThan(20)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("markdown todo checkbox position with monospaced font", async ({ page }) => {
|
||||||
|
await heynotePage.setContent(`
|
||||||
|
∞∞∞markdown
|
||||||
|
- [ ] Test
|
||||||
|
- [x] Test 2
|
||||||
|
`)
|
||||||
|
|
||||||
|
await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first().waitFor()
|
||||||
|
await expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]")).toHaveCount(2)
|
||||||
|
await expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first()).toHaveCSS("position", "absolute")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("markdown todo checkbox position with variable width font", async ({ page }) => {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.queryLocalFonts = async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
family: "Arial",
|
||||||
|
style: "Regular",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
family: "Hack",
|
||||||
|
fullName: "Hack Regular",
|
||||||
|
style: "Regular",
|
||||||
|
postscriptName: "Hack-Regular",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
family: "Hack",
|
||||||
|
fullName: "Hack Italic",
|
||||||
|
style: "Italic",
|
||||||
|
postscriptName: "Hack-Italic",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await page.locator("css=.status-block.settings").click()
|
||||||
|
await page.locator("css=li.tab-appearance").click()
|
||||||
|
await page.locator("css=select.font-family").selectOption("Arial")
|
||||||
|
await page.locator("css=select.font-size").selectOption("20")
|
||||||
|
await page.locator("body").press("Escape")
|
||||||
|
|
||||||
|
await heynotePage.setContent(`
|
||||||
|
∞∞∞markdown
|
||||||
|
- [ ] Test
|
||||||
|
- [x] Test 2
|
||||||
|
`)
|
||||||
|
|
||||||
|
await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first().waitFor()
|
||||||
|
await expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]")).toHaveCount(2)
|
||||||
|
await expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first()).toHaveCSS("position", "relative")
|
||||||
|
})
|
73
tests/custom-key-bindings.spec.js
Normal file
73
tests/custom-key-bindings.spec.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { HeynotePage } from "./test-utils.js";
|
||||||
|
|
||||||
|
|
||||||
|
let heynotePage
|
||||||
|
|
||||||
|
test.beforeEach(async ({page}) => {
|
||||||
|
heynotePage = new HeynotePage(page)
|
||||||
|
await heynotePage.goto()
|
||||||
|
await heynotePage.setContent(`
|
||||||
|
∞∞∞text
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("add custom key binding", async ({page}) => {
|
||||||
|
await page.locator("css=.status-block.settings").click()
|
||||||
|
await page.locator("css=.overlay .settings .dialog .sidebar li.tab-keyboard-bindings").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings")).toBeVisible()
|
||||||
|
await page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-keybinding").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog")).toBeVisible()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog input.keys")).toBeFocused()
|
||||||
|
await page.locator("body").press("Control+Shift+H")
|
||||||
|
await page.locator("body").press("Enter")
|
||||||
|
await page.locator("body").pressSequentially("language")
|
||||||
|
await page.locator(".p-autocomplete-list li.p-autocomplete-option.p-focus").click()
|
||||||
|
await page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog .save").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings table tr.keybind-user")).toHaveCount(1)
|
||||||
|
expect((await heynotePage.getSettings()).keyBindings).toEqual([{key:"Control-Shift-h", command:"openLanguageSelector"}])
|
||||||
|
await page.locator("css=.overlay .settings .dialog .bottom-bar .close").click()
|
||||||
|
await page.locator("body").press("Control+Shift+H")
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("delete custom key binding", async ({page}) => {
|
||||||
|
await page.locator("css=.status-block.settings").click()
|
||||||
|
await page.locator("css=.overlay .settings .dialog .sidebar li.tab-keyboard-bindings").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings")).toBeVisible()
|
||||||
|
await page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-keybinding").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog")).toBeVisible()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog input.keys")).toBeFocused()
|
||||||
|
await page.locator("body").press("Control+Shift+H")
|
||||||
|
await page.locator("body").press("Enter")
|
||||||
|
await page.locator("body").pressSequentially("language")
|
||||||
|
await page.locator(".p-autocomplete-list li.p-autocomplete-option.p-focus").click()
|
||||||
|
await page.locator("css=.settings .tab-content.tab-keyboard-bindings .add-key-binding-dialog .save").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings table tr.keybind-user")).toHaveCount(1)
|
||||||
|
expect((await heynotePage.getSettings()).keyBindings).toEqual([{key:"Control-Shift-h", command:"openLanguageSelector"}])
|
||||||
|
await page.locator("css=.overlay .settings .dialog .bottom-bar .close").click()
|
||||||
|
await page.locator("body").press("Control+Shift+H")
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toBeVisible()
|
||||||
|
|
||||||
|
await page.locator("css=.status-block.settings").click()
|
||||||
|
await page.locator("css=.overlay .settings .dialog .sidebar li.tab-keyboard-bindings").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings")).toBeVisible()
|
||||||
|
await page.locator("css=.settings .tab-content.tab-keyboard-bindings table tr.keybind-user .delete").click()
|
||||||
|
await expect(page.locator("css=.settings .tab-content.tab-keyboard-bindings table tr.keybind-user")).toHaveCount(0)
|
||||||
|
await page.locator("css=.overlay .settings .dialog .bottom-bar .close").click()
|
||||||
|
await page.locator("body").press("Control+Shift+H")
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toHaveCount(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("disable default key binding", async ({page}) => {
|
||||||
|
const langKey = heynotePage.isMac ? "Meta+L" : "Control+L"
|
||||||
|
await page.locator("body").press(langKey)
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toBeVisible()
|
||||||
|
await page.locator("body").press("Escape")
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toHaveCount(0)
|
||||||
|
const settings = await heynotePage.getSettings()
|
||||||
|
settings.keyBindings = [{key:"Mod-L", command:"nothing"}]
|
||||||
|
await heynotePage.setSettings(settings)
|
||||||
|
await page.locator("body").press(langKey)
|
||||||
|
await expect(page.locator("css=.language-selector .items > li.selected")).toHaveCount(0)
|
||||||
|
})
|
@ -13,6 +13,7 @@ test.beforeEach(async ({ page, browserName }) => {
|
|||||||
test.skip()
|
test.skip()
|
||||||
}
|
}
|
||||||
await page.locator("css=.status-block.settings").click()
|
await page.locator("css=.status-block.settings").click()
|
||||||
|
await page.locator("css=li.tab-keyboard-bindings").click()
|
||||||
//await page.locator("css=li.tab-editing").click()
|
//await page.locator("css=li.tab-editing").click()
|
||||||
await page.locator("css=select.keymap").selectOption("emacs")
|
await page.locator("css=select.keymap").selectOption("emacs")
|
||||||
if (heynotePage.isMac) {
|
if (heynotePage.isMac) {
|
||||||
|
@ -20,6 +20,7 @@ export class HeynotePage {
|
|||||||
async goto() {
|
async goto() {
|
||||||
await this.page.goto("/")
|
await this.page.goto("/")
|
||||||
await expect(this.page).toHaveTitle(/Heynote/)
|
await expect(this.page).toHaveTitle(/Heynote/)
|
||||||
|
await expect(this.page.locator(".cm-editor")).toBeVisible()
|
||||||
expect(this.getErrors()).toStrictEqual([])
|
expect(this.getErrors()).toStrictEqual([])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +79,16 @@ export class HeynotePage {
|
|||||||
await this.page.evaluate(({path, content}) => window.heynote.buffer.save(path, content), {path, content:format.serialize()})
|
await this.page.evaluate(({path, content}) => window.heynote.buffer.save(path, content), {path, content:format.serialize()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSettings() {
|
||||||
|
return await this.page.evaluate(() => {
|
||||||
|
return JSON.parse(window.localStorage.getItem("settings") || "{}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSettings(settings) {
|
||||||
|
await this.page.evaluate((settings) => window.heynote.setSettings(settings), settings)
|
||||||
|
}
|
||||||
|
|
||||||
agnosticKey(key) {
|
agnosticKey(key) {
|
||||||
return key.replace("Mod", this.isMac ? "Meta" : "Control")
|
return key.replace("Mod", this.isMac ? "Meta" : "Control")
|
||||||
}
|
}
|
||||||
|
@ -136,8 +136,9 @@ export default defineConfig({
|
|||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
|
api: "modern-compiler",
|
||||||
additionalData: `
|
additionalData: `
|
||||||
@import "./src/css/include.sass"
|
@use "@/src/css/include.sass" as *
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ let initialSettings = {
|
|||||||
showLineNumberGutter: true,
|
showLineNumberGutter: true,
|
||||||
showFoldGutter: true,
|
showFoldGutter: true,
|
||||||
bracketClosing: false,
|
bracketClosing: false,
|
||||||
|
keyBindings: [],
|
||||||
}
|
}
|
||||||
if (settingsData !== null) {
|
if (settingsData !== null) {
|
||||||
initialSettings = Object.assign(initialSettings, JSON.parse(settingsData))
|
initialSettings = Object.assign(initialSettings, JSON.parse(settingsData))
|
||||||
|
@ -2,6 +2,7 @@ import '../src/css/application.sass'
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
|
|
||||||
import App from '../src/components/App.vue'
|
import App from '../src/components/App.vue'
|
||||||
import { loadCurrencies } from '../src/currency'
|
import { loadCurrencies } from '../src/currency'
|
||||||
@ -9,6 +10,7 @@ import { loadCurrencies } from '../src/currency'
|
|||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
app.use(PrimeVue)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
//console.log("test:", app.hej.test)
|
//console.log("test:", app.hej.test)
|
||||||
|
|
||||||
|
@ -37,8 +37,9 @@ export default defineConfig({
|
|||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
|
api: "modern-compiler",
|
||||||
additionalData: `
|
additionalData: `
|
||||||
@import "../src/css/include.sass"
|
@use "@/src/css/include.sass" as *
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user