Merge branch 'main' into patch-1

This commit is contained in:
Jonatan Heyman 2024-07-14 13:16:15 +02:00
commit e84b36273a
24 changed files with 446 additions and 170 deletions

View File

@ -3,6 +3,12 @@
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/heyman/heynote)](https://github.com/heyman/heynote/releases) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/heyman/heynote)](https://github.com/heyman/heynote/releases)
[![Build Status](https://github.com/heyman/heynote/workflows/Tests/badge.svg)](https://github.com/heyman/heynote/actions?query=workflow%3ATests) [![Build Status](https://github.com/heyman/heynote/workflows/Tests/badge.svg)](https://github.com/heyman/heynote/actions?query=workflow%3ATests)
<img src="https://heynote.com/img/logo.png" style="width:79px;">
## General Information
- Website: [heynote.com](https://heynote.com)
- Documentation: [heynote.com](https://heynote.com/docs/)
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.
@ -16,7 +22,7 @@ Available for Mac, Windows, and Linux.
- Block-based - Block-based
- Syntax highlighting: - Syntax highlighting:
C++, C#, Clojure, CSS, Erlang, Go, Groovy, HTML, Java, JavaScript, JSX, Kotlin, TypeScript, TOML, TSX, JSON, Lezer, Markdown, PHP, Python, Ruby, Rust, Shell, SQL, Swift, XML, YAML C++, C#, Clojure, CSS, Erlang, Dart, Go, Groovy, HTML, Java, JavaScript, JSX, Kotlin, TypeScript, TOML, TSX, JSON, Lezer, Markdown, PHP, Python, Ruby, Rust, Shell, SQL, Swift, XML, YAML
- Language auto-detection - Language auto-detection
- Auto-formatting - Auto-formatting
@ -28,19 +34,9 @@ Available for Mac, Windows, and Linux.
- Default or Emacs-like key bindings - Default or Emacs-like key bindings
## Installation ## Documentation
Download the appropriate (Mac, Windows or Linux) version from the latest Github release (or 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). [Documentation](https://heynote.com/docs/) is available on the Heynote website.
### Notes on Linux installation
It's been reported [(#48)](https://github.com/heyman/heynote/issues/48) that ChromeOS's Debian VM need the following packages installed to run the Heynote AppImage:
```
libfuse2
libnss3
libnspr4
```
## Development ## Development
@ -70,49 +66,12 @@ To run the tests in the Playwright UI:
I'm happy to merge contributions that fit my vision for the app. Bug fixes are always welcome. I'm happy to merge contributions that fit my vision for the app. Bug fixes are always welcome.
## Math Blocks
Heynote's Math blocks are powered by [Math.js expressions](https://mathjs.org/docs/expressions). Checkout their [documentation](https://mathjs.org/docs/) to see what [syntax](https://mathjs.org/docs/expressions/syntax.html), [functions](https://mathjs.org/docs/reference/functions.html), and [constants](https://mathjs.org/docs/reference/constants.html) are available.
### Accessing the previous result
The variable `prev` can be used to access the previous result. For example:
```
128
prev * 2 # 256
```
### Changing how the results of Math blocks are formatted?
You can define a custom `format` function within the Math block like this:
```
_format = format # store reference to the built in format
format(x) = _format(x, {notation:"exponential"})
```
You can also do something like this to show the number with your default locale or provide a [custom one](https://www.w3.org/International/articles/language-tags/):
```
format(x) = x.toLocaleString();
format(x) = x.toLocaleString('en-GB');
```
See the [Math.js format()](https://mathjs.org/docs/reference/functions/format.html) function for more info on what's supported.
## FAQ ## FAQ
### Where is the buffer data stored? ### Where is the buffer data stored?
The default paths for the buffer data for the respective OS are: See the [documentation](https://heynote.com/docs/#user-content-the-buffer-file).
- Mac: `~/Library/Application Support/Heynote/buffer.txt`
- Windows: `%APPDATA%\Heynote\buffer.txt`
- Linux: `~/.config/Heynote/buffer.txt`
From version >=1.5.0, symlinks will be supported and you'll be able to configure the path where `buffer.txt` is stored.
### Can you make a mobile app? ### Can you make a mobile app?
@ -126,38 +85,7 @@ I can totally see the usefulness of such a feature, and it's definitely somethin
### What are the default keyboard shortcuts? ### What are the default keyboard shortcuts?
**On Mac** See the [documentation](https://heynote.com/docs/#user-content-default-key-bindings).
```
⌘ + Enter Add new block below the current block
⌥ + Enter Add new block before the current block
⌘ + Shift + Enter Add new block at the end of the buffer
⌥ + Shift + Enter Add new block at the start of the buffer
⌘ + ⌥ + Enter Split the current block at cursor position
⌘ + L Change block language
⌘ + Down Goto next block
⌘ + Up Goto previous block
⌘ + A Select all text in a note block. Press again to select the whole buffer
⌘ + ⌥ + Up/Down Add additional cursor above/below
⌥ + Shift + F Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)
```
**On Windows and Linux**
```
Ctrl + Enter Add new block below the current block
Alt + Enter Add new block before the current block
Ctrl + Shift + Enter Add new block at the end of the buffer
Alt + Shift + Enter Add new block at the start of the buffer
Ctrl + Alt + Enter Split the current block at cursor position
Ctrl + L Change block language
Ctrl + Down Goto next block
Ctrl + Up Goto previous block
Ctrl + A Select all text in a note block. Press again to select the whole buffer
Ctrl + Alt + Up/Down Add additional cursor above/below
Alt + Shift + F Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)
Alt Show menu
```
## Thanks! ## Thanks!

89
docs/index.md Normal file
View File

@ -0,0 +1,89 @@
# Heynote Documentation
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.
The Heynote buffer is divided into blocks, and each block can have its own Language set (e.g. JavaScript, JSON, Markdown, etc.). This gives you syntax highlighting and lets you auto-format that JSON response.
## Default Key Bindings
<!-- keyboard_shortcuts -->
**On Mac**
```
⌘ + Enter Add new block below the current block
⌥ + Enter Add new block before the current block
⌘ + Shift + Enter Add new block at the end of the buffer
⌥ + Shift + Enter Add new block at the start of the buffer
⌘ + ⌥ + Enter Split the current block at cursor position
⌘ + L Change block language
⌘ + Down Goto next block
⌘ + Up Goto previous block
⌘ + A Select all text in a note block. Press again to select the whole buffer
⌘ + ⌥ + Up/Down Add additional cursor above/below
⌥ + Shift + F Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)
```
**On Windows and Linux**
```
Ctrl + Enter Add new block below the current block
Alt + Enter Add new block before the current block
Ctrl + Shift + Enter Add new block at the end of the buffer
Alt + Shift + Enter Add new block at the start of the buffer
Ctrl + Alt + Enter Split the current block at cursor position
Ctrl + L Change block language
Ctrl + Down Goto next block
Ctrl + Up Goto previous block
Ctrl + A Select all text in a note block. Press again to select the whole buffer
Ctrl + Alt + Up/Down Add additional cursor above/below
Alt + Shift + F Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)
Alt Show menu
```
## 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).
### Notes on Linux installation
It's been reported [(#48)](https://github.com/heyman/heynote/issues/48) that ChromeOS's Debian VM need the following packages installed to run the Heynote AppImage:
```
libfuse2
libnss3
libnspr4
```
## Math Blocks
Heynote's Math blocks are powered by [Math.js expressions](https://mathjs.org/docs/expressions). Checkout their [documentation](https://mathjs.org/docs/) to see what [syntax](https://mathjs.org/docs/expressions/syntax.html), [functions](https://mathjs.org/docs/reference/functions.html), and [constants](https://mathjs.org/docs/reference/constants.html) are available.
### Accessing the previous result
The variable `prev` can be used to access the previous result. For example:
```
128
prev * 2 # 256
```
### Changing how the results of Math blocks are formatted
You can define a custom `format` function within the Math block like this:
```
_format = format # store reference to the built in format
format(x) = _format(x, {notation:"exponential"})
```
See the [Math.js format()](https://mathjs.org/docs/reference/functions/format.html) function for more info on what's supported.
## The buffer file
The default paths for the buffer data for the respective operating systems are:
- Mac: `~/Library/Application Support/Heynote/buffer.txt`
- Windows: `%APPDATA%\Heynote\buffer.txt`
- Linux: `~/.config/Heynote/buffer.txt`

View File

@ -35,6 +35,8 @@ const schema = {
"showInMenu": {type: "boolean", default: false}, "showInMenu": {type: "boolean", default: false},
"alwaysOnTop": {type: "boolean", default: false}, "alwaysOnTop": {type: "boolean", default: false},
"bracketClosing": {type: "boolean", default: false}, "bracketClosing": {type: "boolean", default: false},
"defaultBlockLanguage": {type: "string"},
"defaultBlockLanguageAutoDetect": {type: "boolean"},
// when default font settings are used, fontFamily and fontSize is not specified in the // when default font settings are used, fontFamily and fontSize is not specified in the
// settings file, so that it's possible for us to change the default settings in the // settings file, so that it's possible for us to change the default settings in the

View File

@ -131,7 +131,14 @@ const template = [
role: 'help', role: 'help',
submenu: [ submenu: [
{ {
label: 'Learn More', label: 'Documentation',
click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://heynote.com/docs/')
}
},
{
label: 'Website',
click: async () => { click: async () => {
const { shell } = require('electron') const { shell } = require('electron')
await shell.openExternal('https://heynote.com') await shell.openExternal('https://heynote.com')

72
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "Heynote", "name": "Heynote",
"version": "1.7.0", "version": "1.8.0-beta",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "Heynote", "name": "Heynote",
"version": "1.7.0", "version": "1.8.0-beta",
"license": "Commons Clause MIT", "license": "Commons Clause MIT",
"dependencies": { "dependencies": {
"electron-log": "^5.0.1" "electron-log": "^5.0.1"
@ -25,6 +25,7 @@
"@codemirror/lang-python": "^6.1.3", "@codemirror/lang-python": "^6.1.3",
"@codemirror/lang-rust": "^6.0.1", "@codemirror/lang-rust": "^6.0.1",
"@codemirror/lang-sql": "^6.5.4", "@codemirror/lang-sql": "^6.5.4",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-xml": "^6.0.2", "@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "^6.9.3", "@codemirror/language": "^6.9.3",
"@codemirror/legacy-modes": "^6.3.3", "@codemirror/legacy-modes": "^6.3.3",
@ -41,13 +42,13 @@
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"debounce": "^1.2.1", "debounce": "^1.2.1",
"electron": "^28.0.0", "electron": "^31.1.0",
"electron-builder": "^23.6.0", "electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.5.1", "electron-builder-notarize": "^1.5.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",
"fs-jetpack": "^5.1.0", "fs-jetpack": "^5.1.0",
"prettier": "^3.1.1", "prettier": "^3.3.2",
"rollup-plugin-license": "^3.0.1", "rollup-plugin-license": "^3.0.1",
"sass": "^1.57.1", "sass": "^1.57.1",
"typescript": "^4.9.4", "typescript": "^4.9.4",
@ -427,6 +428,20 @@
"@lezer/lr": "^1.0.0" "@lezer/lr": "^1.0.0"
} }
}, },
"node_modules/@codemirror/lang-vue": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
"integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
"dev": true,
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.1"
}
},
"node_modules/@codemirror/lang-xml": { "node_modules/@codemirror/lang-xml": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz",
@ -992,9 +1007,9 @@
"dev": true "dev": true
}, },
"node_modules/@lezer/common": { "node_modules/@lezer/common": {
"version": "1.1.2", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
"integrity": "sha512-V+GqBsga5+cQJMfM0GdnHmg4DgWvLzgMWjbldBg0+jC3k9Gu6nJNZDLJxXEBT1Xj8KhRN4jmbC5CY7SIL++sVw==", "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==",
"dev": true "dev": true
}, },
"node_modules/@lezer/cpp": { "node_modules/@lezer/cpp": {
@ -2262,12 +2277,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -3063,9 +3078,9 @@
"dev": true "dev": true
}, },
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.9", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"jake": "^10.8.5" "jake": "^10.8.5"
@ -3078,14 +3093,14 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "28.0.0", "version": "31.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.0.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-31.1.0.tgz",
"integrity": "sha512-eDhnCFBvG0PGFVEpNIEdBvyuGUBsFdlokd+CtuCe2ER3P+17qxaRfWRxMmksCOKgDHb5Wif5UxqOkZSlA4snlw==", "integrity": "sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^18.11.18", "@types/node": "^20.9.0",
"extract-zip": "^2.0.1" "extract-zip": "^2.0.1"
}, },
"bin": { "bin": {
@ -3474,15 +3489,6 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/electron/node_modules/@types/node": {
"version": "18.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
"integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -3684,9 +3690,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@ -5005,9 +5011,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.1.1", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"

View File

@ -1,6 +1,6 @@
{ {
"name": "Heynote", "name": "Heynote",
"version": "1.7.0", "version": "1.8.0-beta",
"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)",
@ -44,6 +44,7 @@
"@codemirror/lang-python": "^6.1.3", "@codemirror/lang-python": "^6.1.3",
"@codemirror/lang-rust": "^6.0.1", "@codemirror/lang-rust": "^6.0.1",
"@codemirror/lang-sql": "^6.5.4", "@codemirror/lang-sql": "^6.5.4",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-xml": "^6.0.2", "@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "^6.9.3", "@codemirror/language": "^6.9.3",
"@codemirror/legacy-modes": "^6.3.3", "@codemirror/legacy-modes": "^6.3.3",
@ -60,13 +61,13 @@
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"debounce": "^1.2.1", "debounce": "^1.2.1",
"electron": "^28.0.0", "electron": "^31.1.0",
"electron-builder": "^23.6.0", "electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.5.1", "electron-builder-notarize": "^1.5.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",
"fs-jetpack": "^5.1.0", "fs-jetpack": "^5.1.0",
"prettier": "^3.1.1", "prettier": "^3.3.2",
"rollup-plugin-license": "^3.0.1", "rollup-plugin-license": "^3.0.1",
"sass": "^1.57.1", "sass": "^1.57.1",
"typescript": "^4.9.4", "typescript": "^4.9.4",

View File

@ -1,6 +1,6 @@
importScripts("guesslang.min.js") importScripts("guesslang.min.js")
GUESSLANG_LANGUAGES = ["json","py","html","sql","md","java","php","css","xml","cpp","rs","cs","rb","sh","yaml","toml","go","clj","erl","js","ts","swift","kt","groovy","ps1"] GUESSLANG_LANGUAGES = ["json","py","html","sql","md","java","php","css","xml","cpp","rs","cs","rb","sh","yaml","toml","go","clj","erl","js","ts","swift","kt","groovy","ps1","dart"]
const guessLang = new self.GuessLang() const guessLang = new self.GuessLang()

11
public/site.webmanifest Normal file
View File

@ -0,0 +1,11 @@
{
"name": "Heynote",
"short_name": "Heynote",
"icons": [
{
"src": "/icon.ico",
"sizes": "256x256"
}
],
"display": "standalone"
}

View File

@ -122,6 +122,8 @@
:bracketClosing="settings.bracketClosing" :bracketClosing="settings.bracketClosing"
:fontFamily="settings.fontFamily" :fontFamily="settings.fontFamily"
:fontSize="settings.fontSize" :fontSize="settings.fontSize"
:defaultBlockLanguage="settings.defaultBlockLanguage || 'text'"
:defaultBlockLanguageAutoDetect="settings.defaultBlockLanguageAutoDetect === undefined ? true : settings.defaultBlockLanguageAutoDetect"
class="editor" class="editor"
ref="editor" ref="editor"
@openLanguageSelector="openLanguageSelector" @openLanguageSelector="openLanguageSelector"

View File

@ -29,6 +29,8 @@
}, },
fontFamily: String, fontFamily: String,
fontSize: Number, fontSize: Number,
defaultBlockLanguage: String,
defaultBlockLanguageAutoDetect: Boolean,
}, },
components: {}, components: {},
@ -78,6 +80,7 @@
}) })
window._heynote_editor = this.editor window._heynote_editor = this.editor
window.document.addEventListener("currenciesLoaded", this.onCurrenciesLoaded) window.document.addEventListener("currenciesLoaded", this.onCurrenciesLoaded)
this.editor.setDefaultBlockLanguage(this.defaultBlockLanguage, this.defaultBlockLanguageAutoDetect)
// set up buffer change listener // set up buffer change listener
window.heynote.buffer.onChangeCallback((event, content) => { window.heynote.buffer.onChangeCallback((event, content) => {
@ -145,12 +148,18 @@
fontSize() { fontSize() {
this.editor.setFont(this.fontFamily, this.fontSize) this.editor.setFont(this.fontFamily, this.fontSize)
}, },
defaultBlockLanguage() {
this.editor.setDefaultBlockLanguage(this.defaultBlockLanguage, this.defaultBlockLanguageAutoDetect)
},
defaultBlockLanguageAutoDetect() {
this.editor.setDefaultBlockLanguage(this.defaultBlockLanguage, this.defaultBlockLanguageAutoDetect)
},
}, },
methods: { methods: {
setLanguage(language) { setLanguage(language) {
if (language === "auto") { if (language === "auto") {
this.editor.setCurrentLanguage("text", true) this.editor.setCurrentLanguage(null, true)
} else { } else {
this.editor.setCurrentLanguage(language, false) this.editor.setCurrentLanguage(language, false)
} }

View File

@ -1,10 +1,14 @@
<script> <script>
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"
const defaultFontFamily = window.heynote.defaultFontFamily const defaultFontFamily = window.heynote.defaultFontFamily
const defaultFontSize = window.heynote.defaultFontSize const defaultFontSize = window.heynote.defaultFontSize
const defaultDefaultBlockLanguage = "text"
const defaultDefaultBlockLanguageAutoDetect = true
export default { export default {
props: { props: {
@ -39,6 +43,16 @@
bufferPath: this.initialSettings.bufferPath, bufferPath: this.initialSettings.bufferPath,
fontFamily: this.initialSettings.fontFamily || defaultFontFamily, fontFamily: this.initialSettings.fontFamily || defaultFontFamily,
fontSize: this.initialSettings.fontSize || defaultFontSize, fontSize: this.initialSettings.fontSize || defaultFontSize,
languageOptions: LANGUAGES.map(l => {
return {
"value": l.token,
"name": l.token == "text" ? l.name + " (default)" : l.name,
}
}).sort((a, b) => {
return a.name.localeCompare(b.name)
}),
defaultBlockLanguage: this.initialSettings.defaultBlockLanguage || defaultDefaultBlockLanguage,
defaultBlockLanguageAutoDetect: this.initialSettings.defaultBlockLanguageAutoDetect === false ? false : defaultDefaultBlockLanguageAutoDetect,
activeTab: "general", activeTab: "general",
isWebApp: window.heynote.platform.isWebApp, isWebApp: window.heynote.platform.isWebApp,
@ -89,6 +103,8 @@
bufferPath: this.bufferPath, bufferPath: this.bufferPath,
fontFamily: this.fontFamily === defaultFontFamily ? undefined : this.fontFamily, fontFamily: this.fontFamily === defaultFontFamily ? undefined : this.fontFamily,
fontSize: this.fontSize === defaultFontSize ? undefined : this.fontSize, fontSize: this.fontSize === defaultFontSize ? undefined : this.fontSize,
defaultBlockLanguage: this.defaultBlockLanguage === "text" ? undefined : this.defaultBlockLanguage,
defaultBlockLanguageAutoDetect: this.defaultBlockLanguageAutoDetect === true ? undefined : this.defaultBlockLanguageAutoDetect,
}) })
if (!this.showInDock) { if (!this.showInDock) {
this.showInMenu = true this.showInMenu = true
@ -255,6 +271,25 @@
</label> </label>
</div> </div>
</div> </div>
<div class="row">
<div class="entry">
<h2>Default Block Language</h2>
<select v-model="defaultBlockLanguage" @change="updateSettings" class="block-language">
<template v-for="lang in languageOptions" :key="lang.value">
<option :selected="lang.value === defaultBlockLanguage" :value="lang.value">{{ lang.name }}</option>
</template>
</select>
<label>
<input
type="checkbox"
v-model="defaultBlockLanguageAutoDetect"
@change="updateSettings"
class="language-auto-detect"
/>
Auto-detection (default: on)
</label>
</div>
</div>
</TabContent> </TabContent>
<TabContent tab="appearance" :activeTab="activeTab"> <TabContent tab="appearance" :activeTab="activeTab">
@ -417,6 +452,7 @@
overflow-y: auto overflow-y: auto
select select
height: 22px height: 22px
margin: 4px 0
.row .row
display: flex display: flex
.entry .entry

View File

@ -1,7 +1,7 @@
import { ViewPlugin, EditorView, Decoration, WidgetType, lineNumbers } from "@codemirror/view" import { ViewPlugin, EditorView, Decoration, WidgetType, lineNumbers } from "@codemirror/view"
import { layer, RectangleMarker } from "@codemirror/view" import { layer, RectangleMarker } from "@codemirror/view"
import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet} from "@codemirror/state"; import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet} from "@codemirror/state";
import { syntaxTree, ensureSyntaxTree } from "@codemirror/language" import { syntaxTree, ensureSyntaxTree, syntaxTreeAvailable } from "@codemirror/language"
import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js" import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js"
import { IterMode } from "@lezer/common"; import { IterMode } from "@lezer/common";
import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js"; import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js";
@ -10,12 +10,25 @@ import { mathBlock } from "./math.js"
import { emptyBlockSelected } from "./select-all.js"; import { emptyBlockSelected } from "./select-all.js";
function startTimer() {
const timeStart = performance.now();
return function () {
return Math.round(performance.now() - timeStart);
};
}
// tracks the size of the first delimiter // tracks the size of the first delimiter
let firstBlockDelimiterSize let firstBlockDelimiterSize
function getBlocks(state, timeout=50) { /**
* Return a list of blocks in the document from the syntax tree.
* syntaxTreeAvailable() should have been called before this function to ensure the syntax tree is available.
*/
export function getBlocksFromSyntaxTree(state) {
//const timer = startTimer()
const blocks = []; const blocks = [];
const tree = ensureSyntaxTree(state, state.doc.length, timeout) const tree = syntaxTree(state, state.doc.length)
if (tree) { if (tree) {
tree.iterate({ tree.iterate({
enter: (type) => { enter: (type) => {
@ -52,12 +65,90 @@ function getBlocks(state, timeout=50) {
}); });
firstBlockDelimiterSize = blocks[0]?.delimiter.to firstBlockDelimiterSize = blocks[0]?.delimiter.to
} }
//console.log("getBlocksSyntaxTree took", timer(), "ms")
return blocks return blocks
} }
/**
* Parse blocks from document's string contents using String.indexOf()
*/
export function getBlocksFromString(state) {
//const timer = startTimer()
const blocks = []
const doc = state.doc
if (doc.length === 0) {
return [];
}
const content = doc.sliceString(0, doc.length)
const delim = "\n∞∞∞"
let pos = 0
while (pos < doc.length) {
const blockStart = content.indexOf(delim, pos);
if (blockStart != pos) {
console.error("Error parsing blocks, expected delimiter at", pos)
break;
}
const langStart = blockStart + delim.length;
const delimiterEnd = content.indexOf("\n", langStart)
if (delimiterEnd < 0) {
console.error("Error parsing blocks. Delimiter didn't end with newline")
break
}
const langFull = content.substring(langStart, delimiterEnd);
let auto = false;
let lang = langFull;
if (langFull.endsWith("-a")) {
auto = true;
lang = langFull.substring(0, langFull.length - 2);
}
const contentFrom = delimiterEnd + 1;
let blockEnd = content.indexOf(delim, contentFrom);
if (blockEnd < 0) {
blockEnd = doc.length;
}
const block = {
language: {
name: lang,
auto: auto,
},
content: {
from: contentFrom,
to: blockEnd,
},
delimiter: {
from: blockStart,
to: delimiterEnd + 1,
},
range: {
from: blockStart,
to: blockEnd,
},
};
blocks.push(block);
pos = blockEnd;
}
//console.log("getBlocksFromString() took", timer(), "ms")
return blocks;
}
/**
* Get the blocks from the document state.
* If the syntax tree is available, we'll extract the blocks from that. Otherwise
* the blocks are parsed from the string contents of the document, which is much faster
* than waiting for the tree parsing to finish.
*/
export function getBlocks(state) {
if (syntaxTreeAvailable(state, state.doc.length)) {
return getBlocksFromSyntaxTree(state)
} else {
return getBlocksFromString(state)
}
}
export const blockState = StateField.define({ export const blockState = StateField.define({
create(state) { create(state) {
return getBlocks(state, 1000); return getBlocks(state);
}, },
update(blocks, transaction) { update(blocks, transaction) {
// if blocks are empty it likely means we didn't get a parsed syntax tree, and then we want to update // if blocks are empty it likely means we didn't get a parsed syntax tree, and then we want to update

View File

@ -7,7 +7,11 @@ import { selectAll } from "./select-all.js";
export { moveLineDown, moveLineUp, selectAll } export { moveLineDown, moveLineUp, selectAll }
export const insertNewBlockAtCursor = ({ state, dispatch }) => { function getBlockDelimiter(defaultToken, autoDetect) {
return `\n∞∞∞${autoDetect ? defaultToken + '-a' : defaultToken}\n`
}
export const insertNewBlockAtCursor = (editor) => ({ state, dispatch }) => {
if (state.readOnly) if (state.readOnly)
return false return false
@ -16,7 +20,7 @@ export const insertNewBlockAtCursor = ({ state, dispatch }) => {
if (currentBlock) { if (currentBlock) {
delimText = `\n∞∞∞${currentBlock.language.name}${currentBlock.language.auto ? "-a" : ""}\n` delimText = `\n∞∞∞${currentBlock.language.name}${currentBlock.language.auto ? "-a" : ""}\n`
} else { } else {
delimText = "\n∞∞∞text-a\n" delimText = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
} }
dispatch(state.replaceSelection(delimText), dispatch(state.replaceSelection(delimText),
{ {
@ -28,13 +32,12 @@ export const insertNewBlockAtCursor = ({ state, dispatch }) => {
return true; return true;
} }
export const addNewBlockBeforeCurrent = ({ state, dispatch }) => { export const addNewBlockBeforeCurrent = (editor) => ({ state, dispatch }) => {
console.log("addNewBlockBeforeCurrent")
if (state.readOnly) if (state.readOnly)
return false return false
const block = getActiveNoteBlock(state) const block = getActiveNoteBlock(state)
const delimText = "\n∞∞∞text-a\n" const delimText = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
dispatch(state.update({ dispatch(state.update({
changes: { changes: {
@ -50,12 +53,12 @@ export const addNewBlockBeforeCurrent = ({ state, dispatch }) => {
return true; return true;
} }
export const addNewBlockAfterCurrent = ({ state, dispatch }) => { export const addNewBlockAfterCurrent = (editor) => ({ state, dispatch }) => {
if (state.readOnly) if (state.readOnly)
return false return false
const block = getActiveNoteBlock(state) const block = getActiveNoteBlock(state)
const delimText = "\n∞∞∞text-a\n" const delimText = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
dispatch(state.update({ dispatch(state.update({
changes: { changes: {
@ -70,12 +73,12 @@ export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
return true; return true;
} }
export const addNewBlockBeforeFirst = ({ state, dispatch }) => { export const addNewBlockBeforeFirst = (editor) => ({ state, dispatch }) => {
if (state.readOnly) if (state.readOnly)
return false return false
const block = getFirstNoteBlock(state) const block = getFirstNoteBlock(state)
const delimText = "\n∞∞∞text-a\n" const delimText = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
dispatch(state.update({ dispatch(state.update({
changes: { changes: {
@ -91,11 +94,11 @@ export const addNewBlockBeforeFirst = ({ state, dispatch }) => {
return true; return true;
} }
export const addNewBlockAfterLast = ({ state, dispatch }) => { export const addNewBlockAfterLast = (editor) => ({ state, dispatch }) => {
if (state.readOnly) if (state.readOnly)
return false return false
const block = getLastNoteBlock(state) const block = getLastNoteBlock(state)
const delimText = "\n∞∞∞text-a\n" const delimText = getBlockDelimiter(editor.defaultBlockToken, editor.defaultBlockAutoDetect)
dispatch(state.update({ dispatch(state.update({
changes: { changes: {
@ -131,6 +134,10 @@ export function changeLanguageTo(state, dispatch, block, language, auto) {
export function changeCurrentBlockLanguage(state, dispatch, language, auto) { export function changeCurrentBlockLanguage(state, dispatch, language, auto) {
const block = getActiveNoteBlock(state) const block = getActiveNoteBlock(state)
// if language is null, we only want to change the auto-detect flag
if (language === null) {
language = block.language.name
}
changeLanguageTo(state, dispatch, block, language, auto) changeLanguageTo(state, dispatch, block, language, auto)
} }

View File

@ -1,4 +1,4 @@
import { Annotation, EditorState, Compartment } from "@codemirror/state" import { Annotation, EditorState, Compartment, Facet } from "@codemirror/state"
import { EditorView, keymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view" import { EditorView, keymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view"
import { indentUnit, forceParsing, foldGutter } from "@codemirror/language" import { indentUnit, forceParsing, foldGutter } from "@codemirror/language"
import { markdown } from "@codemirror/lang-markdown" import { markdown } from "@codemirror/lang-markdown"
@ -59,6 +59,8 @@ export class HeynoteEditor {
this.deselectOnCopy = keymap === "emacs" this.deselectOnCopy = keymap === "emacs"
this.emacsMetaKey = emacsMetaKey this.emacsMetaKey = emacsMetaKey
this.fontTheme = new Compartment this.fontTheme = new Compartment
this.defaultBlockToken = "text"
this.defaultBlockAutoDetect = true
const state = EditorState.create({ const state = EditorState.create({
doc: content || "", doc: content || "",
@ -84,7 +86,7 @@ export class HeynoteEditor {
}), }),
heynoteLang(), heynoteLang(),
noteBlockExtension(this), noteBlockExtension(this),
languageDetection(() => this.view), languageDetection(() => this),
// set cursor blink rate to 1 second // set cursor blink rate to 1 second
drawSelection({cursorBlinkRate:1000}), drawSelection({cursorBlinkRate:1000}),
@ -206,6 +208,11 @@ export class HeynoteEditor {
}) })
} }
setDefaultBlockLanguage(token, autoDetect) {
this.defaultBlockToken = token
this.defaultBlockAutoDetect = autoDetect
}
formatCurrentBlock() { formatCurrentBlock() {
formatBlockContent({ formatBlockContent({
state: this.view.state, state: this.view.state,

View File

@ -1,7 +1,7 @@
import { keymap } from "@codemirror/view" import { keymap } from "@codemirror/view"
//import { EditorSelection, EditorState } from "@codemirror/state" //import { EditorSelection, EditorState } from "@codemirror/state"
import { import {
indentLess, indentMore, indentLess, indentMore, redo,
} from "@codemirror/commands" } from "@codemirror/commands"
import { import {
@ -48,11 +48,11 @@ export function heynoteKeymap(editor) {
["Mod-x", cutCommand(editor)], ["Mod-x", cutCommand(editor)],
["Tab", indentMore], ["Tab", indentMore],
["Shift-Tab", indentLess], ["Shift-Tab", indentLess],
["Alt-Shift-Enter", addNewBlockBeforeFirst], ["Alt-Shift-Enter", addNewBlockBeforeFirst(editor)],
["Mod-Shift-Enter", addNewBlockAfterLast], ["Mod-Shift-Enter", addNewBlockAfterLast(editor)],
["Alt-Enter", addNewBlockBeforeCurrent], ["Alt-Enter", addNewBlockBeforeCurrent(editor)],
["Mod-Enter", addNewBlockAfterCurrent], ["Mod-Enter", addNewBlockAfterCurrent(editor)],
["Mod-Alt-Enter", insertNewBlockAtCursor], ["Mod-Alt-Enter", insertNewBlockAtCursor(editor)],
["Mod-a", selectAll], ["Mod-a", selectAll],
["Alt-ArrowUp", moveLineUp], ["Alt-ArrowUp", moveLineUp],
["Alt-ArrowDown", moveLineDown], ["Alt-ArrowDown", moveLineDown],
@ -61,6 +61,7 @@ export function heynoteKeymap(editor) {
["Mod-Alt-ArrowDown", newCursorBelow], ["Mod-Alt-ArrowDown", newCursorBelow],
["Mod-Alt-ArrowUp", newCursorAbove], ["Mod-Alt-ArrowUp", newCursorAbove],
["Mod-Shift-k", deleteLine], ["Mod-Shift-k", deleteLine],
["Mod-Shift-z", redo],
{key:"Mod-ArrowUp", run:gotoPreviousBlock, shift:selectPreviousBlock}, {key:"Mod-ArrowUp", run:gotoPreviousBlock, shift:selectPreviousBlock},
{key:"Mod-ArrowDown", run:gotoNextBlock, shift:selectNextBlock}, {key:"Mod-ArrowDown", run:gotoNextBlock, shift:selectNextBlock},
{key:"Ctrl-ArrowUp", run:gotoPreviousParagraph, shift:selectPreviousParagraph}, {key:"Ctrl-ArrowUp", run:gotoPreviousParagraph, shift:selectPreviousParagraph},

View File

@ -11,7 +11,7 @@ NoteDelimiter {
@tokens { @tokens {
noteDelimiterMark { "∞∞∞" } noteDelimiterMark { "∞∞∞" }
NoteLanguage { "text" | "math" | "javascript" | "typescript" | "jsx" | "tsx" | "json" | "python" | "html" | "sql" | "markdown" | "java" | "php" | "css" | "xml" | "cpp" | "rust" | "csharp" | "ruby" | "shell" | "yaml" | "golang" | "clojure" | "erlang" | "lezer" | "toml" | "swift" | "kotlin" | "groovy" | "diff" | "powershell" } NoteLanguage { "text" | "math" | "javascript" | "typescript" | "jsx" | "tsx" | "json" | "python" | "html" | "sql" | "markdown" | "java" | "php" | "css" | "xml" | "cpp" | "rust" | "csharp" | "ruby" | "shell" | "yaml" | "golang" | "clojure" | "erlang" | "lezer" | "toml" | "swift" | "kotlin" | "groovy" | "diff" | "powershell" | "vue" | "dart" }
Auto { "-a" } Auto { "-a" }
noteDelimiterEnter { "\n" } noteDelimiterEnter { "\n" }
//NoteContent { String } //NoteContent { String }

View File

@ -22,7 +22,13 @@ export function configureNesting() {
if (id == NoteContent) { if (id == NoteContent) {
let noteLang = node.node.parent.firstChild.getChildren(NoteLanguage)[0] let noteLang = node.node.parent.firstChild.getChildren(NoteLanguage)[0]
let langName = input.read(noteLang?.from, noteLang?.to) let langName = input.read(noteLang?.from, noteLang?.to)
//console.log("langName:", langName)
// if the NoteContent is empty, we don't want to return a parser, since that seems to cause an
// error for StreamLanguage parsers when the buffer size is large (e.g >300 kb)
if (node.node.from == node.node.to) {
return null
}
if (langName in languageMapping && languageMapping[langName] !== null) { if (langName in languageMapping && languageMapping[langName] !== null) {
//console.log("found parser for language:", langName) //console.log("found parser for language:", langName)
return { return {

View File

@ -10,7 +10,7 @@ export const parser = LRParser.deserialize({
maxTerm: 10, maxTerm: 10,
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 1, repeatNodeCount: 1,
tokenData: "-x~RbYZ!Z}!O!`#V#W!k#W#X$X#X#Y$k#Z#[%Z#[#]%|#^#_&`#_#`'|#`#a(f#a#b)O#d#e)}#f#g+i#g#h+x#h#i,b#l#m&S#m#n-a%&x%&y-g~!`OX~~!cP#T#U!f~!kOU~~!nR#`#a!w#d#e#l#g#h#r~!zP#c#d!}~#QP#^#_#T~#WP#i#j#Z~#^P#f#g#a~#dP#X#Y#g~#lOT~~#oP#d#e#g~#uQ#[#]#{#g#h#g~$OP#T#U$R~$UP#f#g#l~$[P#]#^$_~$bP#Y#Z$e~$hP#Y#Z#g~$nP#f#g$q~$tP#`#a$w~$zP#T#U$}~%QP#b#c%T~%WP#Z#[#g~%^Q#c#d$q#f#g%d~%gP#c#d%j~%mP#c#d%p~%sP#j#k%v~%yP#m#n#g~&PP#h#i&S~&VP#a#b&Y~&]P#`#a#g~&cQ#T#U&i#g#h'm~&lP#j#k&o~&rP#T#U&u~&zPT~#g#h&}~'QP#V#W'T~'WP#f#g'Z~'^P#]#^'a~'dP#d#e'g~'jP#h#i#g~'pQ#c#d'v#l#m#g~'yP#b#c#g~(PP#c#d(S~(VP#h#i(Y~(]P#`#a(`~(cP#]#^'v~(iP#X#Y(l~(oP#n#o(r~(uP#X#Y(x~({P#f#g#g~)RP#T#U)U~)XQ#f#g)_#h#i)w~)bP#_#`)e~)hP#W#X)k~)nP#c#d)q~)tP#k#l'v~)zP#[#]#g~*QR#[#]#l#c#d*Z#m#n+V~*^P#k#l*a~*dP#X#Y*g~*jP#f#g*m~*pP#g#h*s~*vP#[#]*y~*|P#X#Y+P~+SP#`#a&Y~+YP#h#i+]~+`P#[#]+c~+fP#c#d'v~+lP#i#j+o~+rQ#U#V%v#g#h'g~+{R#[#]*y#e#f&Y#k#l,U~,XP#]#^,[~,_P#Y#Z'g~,eS#X#Y,q#c#d&S#g#h,w#m#n,}~,tP#l#m'g~,zP#l#m#g~-QP#d#e-T~-WP#X#Y-Z~-^P#g#h&}~-dP#T#U&S~-jP%&x%&y-m~-pP%&x%&y-s~-xOY~", tokenData: ".[~RcYZ!^}!O!c#V#W!n#W#X$[#X#Y$}#Z#[%m#[#]&`#^#_&r#_#`(Y#`#a(r#a#b)[#d#e*Z#f#g+u#g#h,U#h#i,n#j#k-m#l#m&f#m#n-s%&x%&y-y~!cOX~~!fP#T#U!i~!nOU~~!qR#`#a!z#d#e#o#g#h#u~!}P#c#d#Q~#TP#^#_#W~#ZP#i#j#^~#aP#f#g#d~#gP#X#Y#j~#oOT~~#rP#d#e#j~#xQ#[#]$O#g#h#j~$RP#T#U$U~$XP#f#g#o~$_Q#T#U$e#]#^$q~$hP#f#g$k~$nP#h#i#j~$tP#Y#Z$w~$zP#Y#Z#j~%QP#f#g%T~%WP#`#a%Z~%^P#T#U%a~%dP#b#c%g~%jP#Z#[#j~%pQ#c#d%T#f#g%v~%yP#c#d%|~&PP#c#d&S~&VP#j#k&Y~&]P#m#n#j~&cP#h#i&f~&iP#a#b&l~&oP#`#a#j~&uQ#T#U&{#g#h'y~'OP#j#k'R~'UP#T#U'X~'^PT~#g#h'a~'dP#V#W'g~'jP#f#g'm~'pP#]#^'s~'vP#d#e$k~'|Q#c#d(S#l#m#j~(VP#b#c#j~(]P#c#d(`~(cP#h#i(f~(iP#`#a(l~(oP#]#^(S~(uP#X#Y(x~({P#n#o)O~)RP#X#Y)U~)XP#f#g#j~)_P#T#U)b~)eQ#f#g)k#h#i*T~)nP#_#`)q~)tP#W#X)w~)zP#c#d)}~*QP#k#l(S~*WP#[#]#j~*^R#[#]#o#c#d*g#m#n+c~*jP#k#l*m~*pP#X#Y*s~*vP#f#g*y~*|P#g#h+P~+SP#[#]+V~+YP#X#Y+]~+`P#`#a&l~+fP#h#i+i~+lP#[#]+o~+rP#c#d(S~+xP#i#j+{~,OQ#U#V&Y#g#h$k~,XR#[#]+V#e#f&l#k#l,b~,eP#]#^,h~,kP#Y#Z$k~,qS#X#Y,}#c#d&f#g#h-T#m#n-Z~-QP#l#m$k~-WP#l#m#j~-^P#d#e-a~-dP#X#Y-g~-jP#g#h'a~-pP#i#j#d~-vP#T#U&f~-|P%&x%&y.P~.SP%&x%&y.V~.[OY~",
tokenizers: [0, noteContent], tokenizers: [0, noteContent],
topRules: {"Document":[0,2]}, topRules: {"Document":[0,2]},
tokenPrec: 0 tokenPrec: 0

View File

@ -25,7 +25,7 @@ function cancelIdleCallbackCompat(id) {
} }
} }
export function languageDetection(getView) { export function languageDetection(getEditor) {
const previousBlockContent = {} const previousBlockContent = {}
let idleCallbackId = null let idleCallbackId = null
@ -35,7 +35,8 @@ export function languageDetection(getView) {
if (!event.data.guesslang.language) { if (!event.data.guesslang.language) {
return return
} }
const view = getView() const editor = getEditor()
const view = editor.view
const state = view.state const state = view.state
const block = getActiveNoteBlock(state) const block = getActiveNoteBlock(state)
const newLang = GUESSLANG_TO_TOKEN[event.data.guesslang.language] const newLang = GUESSLANG_TO_TOKEN[event.data.guesslang.language]
@ -88,11 +89,12 @@ export function languageDetection(getView) {
const content = update.state.doc.sliceString(block.content.from, block.content.to) const content = update.state.doc.sliceString(block.content.from, block.content.to)
if (content === "" && redoDepth(update.state) === 0) { if (content === "" && redoDepth(update.state) === 0) {
// if content is cleared, set language to plaintext // if content is cleared, set language to default
const view = getView() const editor = getEditor()
const view = editor.view
const block = getActiveNoteBlock(view.state) const block = getActiveNoteBlock(view.state)
if (block.language.name !== "text") { if (block.language.name !== editor.defaultBlockToken) {
changeLanguageTo(view.state, view.dispatch, block, "text", true) changeLanguageTo(view.state, view.dispatch, block, editor.defaultBlockToken, true)
} }
delete previousBlockContent[idx] delete previousBlockContent[idx]
} }

View File

@ -12,6 +12,7 @@ import { cppLanguage } from "@codemirror/lang-cpp"
import { xmlLanguage } from "@codemirror/lang-xml" import { xmlLanguage } from "@codemirror/lang-xml"
import { rustLanguage } from "@codemirror/lang-rust" import { rustLanguage } from "@codemirror/lang-rust"
import { csharpLanguage } from "@replit/codemirror-lang-csharp" import { csharpLanguage } from "@replit/codemirror-lang-csharp"
import { vueLanguage } from "@codemirror/lang-vue";
import { StreamLanguage } from "@codemirror/language" import { StreamLanguage } from "@codemirror/language"
import { ruby } from "@codemirror/legacy-modes/mode/ruby" import { ruby } from "@codemirror/legacy-modes/mode/ruby"
@ -22,18 +23,18 @@ import { clojure } from "@codemirror/legacy-modes/mode/clojure"
import { erlang } from "@codemirror/legacy-modes/mode/erlang" import { erlang } from "@codemirror/legacy-modes/mode/erlang"
import { toml } from "@codemirror/legacy-modes/mode/toml" import { toml } from "@codemirror/legacy-modes/mode/toml"
import { swift } from "@codemirror/legacy-modes/mode/swift" import { swift } from "@codemirror/legacy-modes/mode/swift"
import { kotlin } from "@codemirror/legacy-modes/mode/clike" import { kotlin, dart } from "@codemirror/legacy-modes/mode/clike"
import { groovy } from "@codemirror/legacy-modes/mode/groovy" import { groovy } from "@codemirror/legacy-modes/mode/groovy"
import { diff } from "@codemirror/legacy-modes/mode/diff"; import { diff } from "@codemirror/legacy-modes/mode/diff";
import { powerShell } from "@codemirror/legacy-modes/mode/powershell"; import { powerShell } from "@codemirror/legacy-modes/mode/powershell";
import typescriptPlugin from "prettier/plugins/typescript.mjs" import typescriptPlugin from "prettier/plugins/typescript"
import babelPrettierPlugin from "prettier/plugins/babel.mjs" import babelPrettierPlugin from "prettier/plugins/babel"
import htmlPrettierPlugin from "prettier/esm/parser-html.mjs" import htmlPrettierPlugin from "prettier/plugins/html"
import cssPrettierPlugin from "prettier/esm/parser-postcss.mjs" import cssPrettierPlugin from "prettier/plugins/postcss"
import markdownPrettierPlugin from "prettier/esm/parser-markdown.mjs" import markdownPrettierPlugin from "prettier/plugins/markdown"
import yamlPrettierPlugin from "prettier/plugins/yaml.mjs" import yamlPrettierPlugin from "prettier/plugins/yaml"
import * as prettierPluginEstree from "prettier/plugins/estree.mjs"; import * as prettierPluginEstree from "prettier/plugins/estree";
class Language { class Language {
@ -253,6 +254,18 @@ export const LANGUAGES = [
parser: StreamLanguage.define(powerShell).parser, parser: StreamLanguage.define(powerShell).parser,
guesslang: "ps1", guesslang: "ps1",
}), }),
new Language({
token: "vue",
name: "Vue",
parser: vueLanguage.parser,
guesslang: null,
}),
new Language({
token: "dart",
name: "Dart",
parser: StreamLanguage.define(dart).parser,
guesslang: "dart",
}),
] ]

View File

@ -114,3 +114,32 @@ const runTest = async (page, key, expectedBlocks) => {
await expect(await page.locator("css=.heynote-block-start.first")).toHaveCount(1) await expect(await page.locator("css=.heynote-block-start.first")).toHaveCount(1)
} }
test("test custom default block language", async ({ page, browserName }) => {
heynotePage.setContent(`
text
Text block`)
await page.locator("css=.status-block.settings").click()
await page.locator("css=li.tab-editing").click()
await page.locator("css=select.block-language").selectOption("Rust")
await page.locator("body").press("Escape")
await page.locator("body").press((heynotePage.isMac ? "Meta" : "Control") + "+Enter")
expect(await heynotePage.getContent()).toBe(`
text
Text block
rust-a
`)
await page.locator("css=.status-block.settings").click()
await page.locator("css=li.tab-editing").click()
await page.locator("css=input.language-auto-detect").click()
await page.locator("body").press("Escape")
await page.locator("body").press((heynotePage.isMac ? "Meta" : "Control") + "+Enter")
expect(await heynotePage.getContent()).toBe(`
text
Text block
rust-a
rust
`)
})

View File

@ -0,0 +1,28 @@
import { expect, test } from "@playwright/test"
import { EditorState } from "@codemirror/state"
import { heynoteLang } from "../src/editor/lang-heynote/heynote.js"
import { getBlocksFromSyntaxTree, getBlocksFromString } from "../src/editor/block/block.js"
test("parse blocks from both syntax tree and string contents", async ({page}) => {
const contents = `
text
Text Block A
text-a
Text Block B
json-a
{
"key": "value"
}
python
print("Hello, World!")
`
const state = EditorState.create({
doc: contents,
extensions: heynoteLang(),
})
const treeBlocks = getBlocksFromSyntaxTree(state)
const stringBlocks = getBlocksFromString(state)
expect(treeBlocks).toEqual(stringBlocks)
})

View File

@ -17,10 +17,8 @@ rmSync('dist-electron', { recursive: true, force: true })
const isDevelopment = process.env.NODE_ENV === "development" || !!process.env.VSCODE_DEBUG const isDevelopment = process.env.NODE_ENV === "development" || !!process.env.VSCODE_DEBUG
const isProduction = process.env.NODE_ENV === "production" const isProduction = process.env.NODE_ENV === "production"
const updateReadmeKeybinds = async () => { const injectKeybindsInDocs = async () => {
const readmePath = path.resolve(__dirname, 'README.md') const keybindsRegex = /^(<!-- keyboard_shortcuts -->\s*).*?^(```\s+#)/gms
let readme = fs.readFileSync(readmePath, 'utf-8')
const keybindsRegex = /^(### What are the default keyboard shortcuts\?\s*).*?^(```\s+#)/gms
const shortcuts = `$1**On Mac** const shortcuts = `$1**On Mac**
\`\`\` \`\`\`
@ -32,8 +30,10 @@ ${keyHelpStr('darwin')}
\`\`\` \`\`\`
${keyHelpStr('win32')} ${keyHelpStr('win32')}
$2` $2`
readme = readme.replace(keybindsRegex, shortcuts) const docsPath = path.resolve(__dirname, 'docs', 'index.md')
fs.writeFileSync(readmePath, readme) let docs = fs.readFileSync(docsPath, 'utf-8')
docs = docs.replace(keybindsRegex, shortcuts)
fs.writeFileSync(docsPath, docs)
} }
const updateGuesslangLanguagesInWebWorker = async () => { const updateGuesslangLanguagesInWebWorker = async () => {
@ -56,7 +56,7 @@ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
updateReadmeKeybinds(), injectKeybindsInDocs(),
updateGuesslangLanguagesInWebWorker(), updateGuesslangLanguagesInWebWorker(),
electron([ electron([
{ {

View File

@ -5,6 +5,7 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<link rel="manifest" href="/site.webmanifest">
<title>Heynote</title> <title>Heynote</title>
</head> </head>
<body> <body>