Skip to content
Merged

Dev #59

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions builder/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

361 changes: 120 additions & 241 deletions client/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codechat-editor-client",
"version": "0.1.10",
"version": "0.1.11",
"description": "The CodeChat Editor Client, part of a web-based literate programming editor (the CodeChat Editor).",
"homepage": "https://github.com/bjones1/CodeChat_Editor",
"type": "module",
Expand Down
14 changes: 6 additions & 8 deletions client/src/CodeChatEditor.mts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
CodeMirror_load,
CodeMirror_save,
mathJaxTypeset,
mathJaxUnTypeset,
} from "./CodeMirror-integration.mjs";
import "./EditorComponents.mjs";
import "./graphviz-webcomponent-setup.mts";
Expand Down Expand Up @@ -301,11 +302,7 @@ const save_lp = async () => {
const codechat_body = document.getElementById(
"CodeChat-body",
) as HTMLDivElement;
window.MathJax.startup.document
.getMathItemsWithin(codechat_body)
.forEach((item: any) => {
item.removeFromDocument(true);
});
mathJaxUnTypeset(codechat_body);
// To save a document only, simply get the HTML from the only Tiny MCE
// div.
tinymce.activeEditor!.save();
Expand All @@ -316,7 +313,7 @@ const save_lp = async () => {
mathJaxTypeset(codechat_body);
} else {
source = CodeMirror_save();
await codechat_html_to_markdown(source);
codechat_html_to_markdown(source);
}

let update: UpdateMessageContents = {
Expand All @@ -332,7 +329,8 @@ const save_lp = async () => {
return update;
};

// Per[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples),
// Per
// [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples),
// here's the least bad way to choose between the control key and the command
// key.
const os_is_osx =
Expand All @@ -357,7 +355,7 @@ const on_save = async (only_if_dirty: boolean = false) => {
is_dirty = false;
};

const codechat_html_to_markdown = async (source: any) => {
const codechat_html_to_markdown = (source: any) => {
const entries = source.doc_blocks.entries();
for (const [index, doc_block] of entries) {
const wordWrapMargin = Math.max(
Expand Down
92 changes: 65 additions & 27 deletions client/src/CodeMirror-integration.mts
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,13 @@ class DocBlockWidget extends WidgetType {
// Handle both cases.
const [contents_div, is_tinymce] = get_contents(dom);
if (is_tinymce) {
tinymce_singleton!.setContent(this.contents);
ignore_next_dirty = true;
tinymce_singleton!.setContent(this.contents);
tinymce_singleton!.save();
} else {
contents_div.innerHTML = this.contents;
mathJaxTypeset(contents_div);
}
mathJaxTypeset(contents_div);

// Indicate the update was successful.
return true;
Expand All @@ -380,12 +380,8 @@ class DocBlockWidget extends WidgetType {
destroy(dom: HTMLElement): void {
// If this is the TinyMCE editor, save it.
const [contents_div, is_tinymce] = get_contents(dom);
// Revert the typeset math to its original form.
window.MathJax.startup.document
.getMathItemsWithin(contents_div)
.forEach((item: any) => {
item.removeFromDocument(true);
});
// Forget about any typeset math in this node.
window.MathJax.typesetClear([contents_div]);
if (is_tinymce) {
const codechat_body = document.getElementById("CodeChat-body")!;
const tinymce_div = document.getElementById("TinyMCE-inst")!;
Expand All @@ -396,12 +392,28 @@ class DocBlockWidget extends WidgetType {

// Typeset the provided node; taken from the [MathJax
// docs](https://docs.mathjax.org/en/latest/web/typeset.html#handling-asynchronous-typesetting).
export const mathJaxTypeset = (node: HTMLElement) => {
window.MathJax.typesetPromise([node]).catch((err: any) =>
console.log("Typeset failed: " + err.message),
);
export const mathJaxTypeset = async (
// The node to typeset.
node: HTMLElement,
// An optional function to run when the typeset finishes.
afterTypesetFunc: () => void = () => {}) => {
try {
await window.MathJax.typesetPromise([node]);
afterTypesetFunc();
} catch(err: any) {
console.log("Typeset failed: " + err.message);
}
};

// Transform a typeset node back to the original (untypeset) text.
export const mathJaxUnTypeset = (node: HTMLElement) => {
window.MathJax.startup.document
.getMathItemsWithin(node)
.forEach((item: any) => {
item.removeFromDocument(true);
});
}

// Given a doc block div element, return the contents div and if TinyMCE is
// attached to that div.
const get_contents = (element: HTMLElement): [HTMLDivElement, boolean] => {
Expand All @@ -412,10 +424,12 @@ const get_contents = (element: HTMLElement): [HTMLDivElement, boolean] => {

// Determine if the element which generated the provided event was in a doc
// block or not. If not, return false; if so, return the doc block div.
const event_is_in_doc_block = (event: Event): boolean | HTMLDivElement => {
const target = event.target as HTMLElement;
const element_is_in_doc_block = (target: EventTarget | null): boolean | HTMLDivElement => {
if (target === null) {
return false;
}
// Look for either a CodeMirror ancestor or a CodeChat doc block ancestor.
const ancestor = target.closest(".cm-line, .CodeChat-doc");
const ancestor = (target as HTMLElement).closest(".cm-line, .CodeChat-doc");
// If it's a doc block, then tell Code Mirror not to handle this event.
if (ancestor?.classList.contains("CodeChat-doc")) {
return ancestor as HTMLDivElement;
Expand All @@ -442,12 +456,6 @@ const on_dirty = (
const indent = indent_div.innerHTML;
const delimiter = indent_div.getAttribute("data-delimiter")!;
const [contents_div, is_tinymce] = get_contents(target);
// Revert the typeset math to its original form.
window.MathJax.startup.document
.getMathItemsWithin(contents_div)
.forEach((item: any) => {
item.removeFromDocument(true);
});
tinymce_singleton!.save();
const content = is_tinymce
? tinymce_singleton!.getContent()
Expand All @@ -464,8 +472,6 @@ const on_dirty = (

current_view.dispatch({ effects });

// Re-typeset.
mathJaxTypeset(contents_div);
return false;
};

Expand All @@ -480,8 +486,8 @@ const DocBlockPlugin = ViewPlugin.fromClass(
// so it can be edited. A simpler alternative is to do this in the
// update() method above, but this is VERY slow, since update is
// called frequently.
focusin: (event: Event, view: EditorView) => {
const target_or_false = event_is_in_doc_block(event);
focusin: (event: FocusEvent, view: EditorView) => {
const target_or_false = element_is_in_doc_block(event.target);
if (!target_or_false) {
return false;
}
Expand Down Expand Up @@ -514,7 +520,11 @@ const DocBlockPlugin = ViewPlugin.fromClass(

// See if this is already a TinyMCE instance; if not, move it
// here.
if (!is_tinymce) {
if (is_tinymce) {
ignore_next_dirty = true;
mathJaxUnTypeset(contents_div);
ignore_next_dirty = false;
} else {
// Wait until the focus event completes; this causes the
// cursor position (the selection) to be set in the
// contenteditable div. Then, save that location.
Expand Down Expand Up @@ -591,8 +601,15 @@ const DocBlockPlugin = ViewPlugin.fromClass(
// Move TinyMCE to the new location, then remove the old
// div it will replace.
target.insertBefore(tinymce_div, null);
tinymce_singleton!.setContent(contents_div.innerHTML);
// TinyMCE edits booger MathJax. Also, the math is
// uneditable. So, translate it back to its untypeset
// form. When editing is done, it will be re-rendered.
mathJaxUnTypeset(contents_div);

// Setting the content makes TinyMCE consider it dirty
// -- ignore this "dirty" event.
ignore_next_dirty = true;
tinymce_singleton!.setContent(contents_div.innerHTML);
tinymce_singleton!.save();
contents_div.remove();

Expand Down Expand Up @@ -833,6 +850,27 @@ export const CodeMirror_load = async (
}
on_dirty(target_or_false);
});
// When leaving a TinyMCE block, retypeset the math. (It's
// untypeset when entering the block, to avoid editing
// problems.)
editor.on("focusout", (event: any) => {
const target_or_false = event.target;
if (target_or_false == null) {
return false;
}
if (!tinymce_singleton!.isDirty()) {
ignore_next_dirty = true;
}
// When switching from one doc block to another, the MathJax
// typeset finishes after the new doc block has been
// updated. To prevent saving the "dirty" content from
// typesetting, wait until this finishes to clear the
// `ignore_next_dirty` flag.
mathJaxTypeset(target_or_false, () => {
tinymce_singleton!.save();
ignore_next_dirty = false;
});
})
},
})
)[0];
Expand Down
7 changes: 6 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ Changelog

* [Github master](https://github.com/bjones1/CodeChat_Editor):
* No changes.
* v0.1.10, 2025-Feb-20: 
* v0.1.11, 2025-Feb-27:
* Fixed data corruption while editing math: typeset math, instead of LaTeX
source, was saved to the source file. Now, math is untypeset during
edits, then retypeset afterwards.
* Correctly handle webview shutdown in VSCode extension.
* v0.1.10, 2025-Feb-20:
* Update to the 2024 editing of Rust.
* Update dependencies.
* Update source formatting using current CodeChat Editor.
Expand Down
16 changes: 15 additions & 1 deletion docs/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,4 +569,18 @@ JavaScript functions are a
Therefore, we use only arrow functions for this codebase.

Other than that, follow the [MDN style
guide](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/JavaScript).
guide](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/JavaScript).

Misc
----

Eventaully, provide a read-only mode with possible auth (restrict who can view)
using JWTs; see [one
approach](https://auth0.com/blog/build-an-api-in-rust-with-jwt-authentication-using-actix-web/).

A better approach to make macros accessible where they're defined, instead of at
the crate root: see
[SO](https://stackoverflow.com/questions/26731243/how-do-i-use-a-macro-across-module-files/67140319#67140319).

When using VSCode with Rust, set `"rust-analyzer.cargo.targetDir": true`. See
[this issue](https://github.com/rust-lang/rust-analyzer/issues/17807).
Loading
Loading