Demo of ProseMirror WYSIWYG editor#8991
Conversation
|
Confirmed: linonetwo has already signed the Contributor License Agreement (see contributing.md) |
|
Crawl all prosemirror as dependencies using this JS, and download https://cdn.jsdelivr.net/npm/prosemirror-view@latest/style/prosemirror.css and https://cdn.jsdelivr.net/npm/prosemirror-menu@latest/style/menu.css manually. const fs = require('fs');
const path = require('path');
const dependencies = [
'prosemirror-state',
'prosemirror-view',
'prosemirror-model',
'prosemirror-schema-basic',
'prosemirror-schema-list',
'prosemirror-example-setup',
'orderedmap',
'prosemirror-transform',
'prosemirror-keymap',
'prosemirror-history',
'prosemirror-commands',
'prosemirror-dropcursor',
'prosemirror-gapcursor',
'prosemirror-menu',
'prosemirror-inputrules',
'w3c-keyname',
'crelt',
'rope-sequence',
];
async function getVersionAndDownload(dep) {
try {
const response = await fetch(`https://registry.npmjs.org/${dep}/latest`);
const data = await response.json();
const version = data.version;
// const url = `https://cdn.jsdelivr.net/npm/${dep}@${version}/dist/index.cjs`;
const url = `https://cdn.jsdelivr.net/npm/${dep}@${version}`;
console.log(`Downloading ${dep}@${version} from ${url}`);
const fileResponse = await fetch(url);
const reader = fileResponse.body.getReader();
const writer = fs.createWriteStream(path.resolve(__dirname, 'plugins/tiddlywiki/prosemirror/files', `${dep}.cjs`));
const stream = new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
controller.enqueue(value);
}
controller.close();
}
});
const responseStream = stream.pipeTo(new WritableStream({
write(chunk) {
writer.write(chunk);
},
close() {
writer.end();
}
}));
await responseStream;
} catch (error) {
console.error(`Failed to download ${dep}:`, error);
}
}
async function main() {
for (const dep of dependencies) {
await getVersionAndDownload(dep);
}
console.log('All files downloaded and renamed successfully.');
}
main();place it on tiddlywiki repo's root as |
|
Where is the netlify preview? Seems it won't build preview site for draft PR. |
✅ Deploy Preview for tiddlywiki-previews ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
39d80b3 to
de0c989
Compare
2d88859 to
04acfb2
Compare
📊 Build Size Comparison:
|
| Branch | Size |
|---|---|
| Base (master) | 2528.4 KB |
| PR | 2533.1 KB |
Diff: ⬆️ Increase: +4.7 KB
|
@linonetwo -- Can you make this PR work with the latest changes in PR #8258 ? |
d467ecc to
033dbca
Compare
|
I rebased this onto #8258 , hope didn't break anything. |
|
I think, the new plugin needs to be included, for prosemirror to work here |
|
Now it works, see https://deploy-preview-8991--tiddlywiki-previews.netlify.app/ This is just an demo showing #8258 works. Future routes:
and after this choise, there are more to improve
I could complete these features in a double-month, but according to current PR merging speed, it might takes several years if it becomes a core plugin. So I'd like to make it a 3rd party plugin in https://github.com/tiddly-gittly/ |
I know. But it is a good test, to see, what can be achieved.
I did have a look at the Modern.TiddlyDev dependency list. For me that's very concerning.
And I think none of them is really needed. TW can handle to compile plugins right from the core. TW itself is built that way. I use it every day. As Jeremy pointed out. ProseMirror is already pretty heavy weight on its own. Adding additional additional dependencies maintenance nightmare and IMO looks a bit like over-engineering. IMO Adding features like "slash menu" as plugin to TW is OK, but as a sub-plugin, it is way to strongly coupled. It has nothing to do with ProseMirror. It can support ProseMirror, but it should not be linked. Just my personal thoughts. |
|
@Jermolene When you merge the upstream ast core plugin PR, I'd like to move this WYSIWYG plugin to another repo, because I'm tired of writing JS when working on this, I want to use TS, so GitHub Copilot can auto-fix bugs more efficiently and the human-computer feedback loop will be faster. But you said you hope the WYSIWYG editor to be a core plugin, so I'm in a dilemma. Some afterthoughts: I must have been spoiled by modern tools; I haven't written code by hand for quite some time.. And it is my fault to use only gpt4.1 on tiddlywiki, I should have use claude4 like I'm used during the work. That won't have problem writing JS. While it is a bit more expensive... |
0d5cc89 to
1c87da1
Compare
| @@ -177,25 +177,25 @@ SlashMenuUI.prototype.createMenuItem = function(element, state) { | |||
|
|
|||
| SlashMenuUI.prototype.getIconForElement = function(element) { | |||
| // Simple text icons for now - can be enhanced with SVG later | |||
There was a problem hiding this comment.
Will they ever be SVG icons "later" -- At least add a TODO the comment, so anyone may find it later ;)
There was a problem hiding this comment.
I haven't decide how slash menu looks like, I may copy notion's style and add icons.
| filter: data | ||
| }); | ||
| } | ||
| return true; // Prevent the composition from being inserted |
There was a problem hiding this comment.
Why are the comments removed. If they are needed to better understand the code IMO they should stay.
|
I'm putting more time on AI-writing tiddler recently. So effort on this will be liminted in the future. So I will try migrate to https://tiptap.dev/docs/editor/getting-started/install/vanilla-javascript , which is also prosemirror based, but will save a lot of my time. |
|
Thanks @linonetwo. I am still very keen to be able to offer a WYSIWYG editor. If it's not ready for v5.4.0 then it would make an excellent tentpole feature for v5.5.0. |
|
@linonetwo .. I did have a new look at the demo page: The example text with ordered and unordered lists seems to have a lot of space in between, which IMO should not be there. The result does not have that spacing, so it's not WYSIWIG anymore.
|
bea743a to
f6c1cc9
Compare
- Remove IIFE wrapper in dom-to-image/startup.js - Convert 2-space to tab indentation in prosemirror/files/*.cjs (17 files) - Replace var with const/let in new code across pragma.js, nodeview.js, wikiparser.js, and test-wikitext-blanklines.js - Use ES2017+ const/let syntax consistently in new/modified code All 1605 Jasmine specs pass.
b7891bc to
6db74be
Compare
The widget root node contenteditable=false fix (30e9926) was correct but incomplete. The browser's native selection painting still covers rendered widget content because prosemirror-view.cjs injects its own ::selection rule that only hides the background, leaving the foreground color to inherit from the global ::selection { color: highlighttext } (white). Two-layer fix: ::selection rules so link text retains its own color. 2. JS: explicitly clear any residual native selection range after selectBlockNode() in handleRenderedContentMouseDown. Also rebuild test.html so Playwright tests use the updated CSS.
The CSS tiddler color:inherit fix can be overridden by the same selector injected by prosemirror-view.cjs at runtime. Inject the rule from JavaScript after EditorView creation so it always appears last in the DOM and wins the cascade.
… in hideselection The previous fix (color: inherit) does not work in Chromium's ::selection pseudo-element because 'inherit' there falls back to the system's highlighttext color (white) rather than the element's own color. Use 'currentColor' instead, which correctly resolves to the element's computed color value in all browsers. Also removed redundant workarounds: - removeAllRanges() in handleRenderedContentMouseDown (ProseMirror recreates the native selection immediately via selectionchange listener) - JS-injected CSS override in engine.js (unnecessary once CSS file is correct) Tests updated to verify currentColor rule exists and that selected widget links do not turn white.
commitEdit() was missing a renderViewMode() call after saveEdit(), leaving the edit textarea visible after clicking save. cancelEdit() already had this call. All 77 tests now pass.
Add tabindex attribute and focus delegation to the ProseMirror wrapper DOM node, matching the behavior of SimpleEngine and FramedEngine. Without this, the editor was not keyboard-focusable via Tab, making it impossible for users to Tab from the title field into the editor. - Set tabindex from widget.editTabIndex (defaults to EditTabIndex config) - Delegate focus events from wrapper to ProseMirror EditorView.focus()
In auto-height mode, the ProseMirror editor wrapper only cleared height and overflow without setting a minHeight, causing the editor to collapse to a minimal height on empty or short content. This made it difficult for users to click into the editor. Set minHeight from widget.editMinHeight (defaults to 100px, configurable via minHeight widget attribute) to match SimpleEngine/FramedEngine behavior where resizeTextAreaToFit enforces a minimum height.
The two "Preview toggle should preserve custom syntax" tests in source-panel.spec.js were flaky due to two race conditions: 1. Playwright's click() hangs: Clicking the "Edit this tiddler" button triggers TiddlyWiki's SPA hash navigation. Playwright detects this as a navigation event and waits for a load event that never fires, causing a 15-60s timeout. 2. Label check after edit mode: After entering edit mode, the custom syntax widget view-mode rendering is replaced by the ProseMirror editor. The waitForLabelOccurrences(1) check was racing against this DOM update. Fix: Replace the Playwright button click with a direct call to the navigator widget's handleEditTiddlerEvent() via page.evaluate(). This bypasses Playwright's navigation detection while properly triggering TiddlyWiki's draft creation. Wait for the preview pane button to appear as a reliable signal that edit mode has fully loaded. Verified stable: 25/25 passes with --repeat-each=5 (was 1-5 failures per 5 runs before the fix).
Add real-time conversion for TW native inline syntax:
- [[Target]] / [[Display|Target]] → link mark
- ''bold'', //italic//, __underline__, ^^sup^^, ,,sub,,, ~~strike~~, `code` → respective marks
Changes:
- inputrules.js: refactor with declarative WIKITEXT_INLINE_MARK_RULES
array and per-rule/global config via tiddlers
- base-setup.js / plugin-list.js: pass wiki and editorType to
buildInputRules for config lookup
- misc.spec.js: add Playwright e2e tests for wikilink, bold, code
- test-prosemirror-inputrules.js: add Node unit tests for regex
patterns and config behavior
Note: //italic// rule conflicts with SlashMenuPlugin (skipped test),
to be resolved separately.
handler guards checking doc[start-1] for ES2017 compatibility - Add Ctrl+0 shortcut (alongside existing Shift-Ctrl-0) to reset current line to plain paragraph text - Update corresponding tests to match new regex/handler behavior - Fixes CI lint errors from eslint es-x/no-regexp-lookbehind-assertions
…t and supports undo - handleMakeLink: when no text param (linkify toolbar), use selected text as href Previously returned false, falling back to native TW text-op path which miscalculates selection and rebuilds the entire editor (losing undo history). - handleMakeLink: removed linkTarget guard so any non-empty selection triggers link creation with the selected text as the link target. - Add Playwright tests for linkify correctness and undo after toolbar operations.
Move temporary core-module overrides and shadow tiddlers into a dedicated patch/ directory so they can be easily deleted once the corresponding core changes are merged upstream. Changes: - Move core/debounce.js -> patch/debounce.js (shadows core debounce utility) - Move preview-type-dropdown.tid from toolbar/ to patch/ (shadow override) - Move patch/startup.js into patch/ (monkey-patches for edit.js, factory.js, wikiparser.js) - Update widget.js and engine.js to require $:/core/modules/utils/debounce.js - Add patch/README.tid listing all pending core merges - Remove unused origGetEditorType variable in patch/startup.js
…-shared.js Move duplicated parse/serialize/external-sync/rebuild logic from the factory engine and the standalone <-prosemirror> widget into a single library module. Both entry points now share the same wikitext-to-ProseMirror and save-path code, while keeping their own DOM lifecycle, image picker, and tiddler-change adapters.
… and list items, drop unwrap outside list, add tests
… blocks accordingly - Add inside-list drop zone detection: pointer over list item content area wraps non-list blocks into a new list item; pointer over marker/gutter keeps the block as a sibling (no wrapping) - Fix buildMoveTransaction for inside-list drops: auto-wrap non-list blocks into a list item to preserve list structure - Fix isInsideList boundary check at list end position - Add findListAncestor / getListContentBox helper functions - Extend test coverage: dropping into a list item wraps, dropping outside keeps type
44d6f67 to
2a0bef7
Compare
|
When the latest version of prosemirroe is used together with tiddlers\system$__xp.json, pressing the shortcut key to insert |

Demo: https://deploy-preview-8991--tiddlywiki-previews.netlify.app/
This is based on #8258 , since it is not merged, commits here will be long.
Demo to show it is hard to manage dependency without a bundler like https://tiddly-gittly.github.io/Modern.TiddlyDev/
Script to crawl all JS dependency: #8991 (comment)
But due to those
.cjsfile stillrequireeach other with names likerequire('prosemirror-state'), I have to keep its nameprosemirror-stateintiddlywiki.files. Any solution for this? An idea is use regexp ontiddlywiki.filesto auto replacerequire("xxxon prosemirror js files, torequire("$:/plugins/tiddlywiki/prosemirror/lib/xxx.And I don't quite understand
engine.jsedit-codemirror.jsin the old codemirror plugin, so I make prosemirror a widget instead, is this OK?