mirror of
https://github.com/heyman/heynote.git
synced 2025-06-28 13:31:38 +02:00
Fix issue with positioning and size of todo list checkboxes in Markdown blocks
Use two different ways of positioning the checkbox depending on if the font is monospaced or not
This commit is contained in:
parent
50f3cae372
commit
85f59661e9
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
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.1.4 (not released yet)
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
## 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)
|
||||||
|
@ -8,3 +8,4 @@ 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"
|
||||||
|
@ -12,7 +12,7 @@ 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 { 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 { heynoteKeymap } from "./keymap.js"
|
||||||
@ -135,6 +135,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() {
|
||||||
@ -258,6 +263,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)],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
'.cm-scroller': {
|
return [
|
||||||
fontFamily: fontFamily || window.heynote.defaultFontFamily,
|
EditorView.theme({
|
||||||
fontSize: (fontSize) + "px",
|
'.cm-scroller': {
|
||||||
},
|
fontFamily: computedFontFamily,
|
||||||
})
|
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,44 @@ 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 { 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.style.position = "absolute"
|
box.style.margin = "0"
|
||||||
box.style.top = "-3px"
|
box.style.padding = "0"
|
||||||
box.style.left = "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.top = "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 +71,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,8 +111,9 @@ export const todoCheckboxPlugin = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
if (update.docChanged || update.viewportChanged)
|
if (update.docChanged || update.viewportChanged || update.transactions.some(tr => tr.annotations.some(a => a.value === SET_FONT))) {
|
||||||
this.decorations = checkboxes(update.view)
|
this.decorations = checkboxes(update.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
decorations: v => v.decorations,
|
decorations: v => v.decorations,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user