all tests pass but only if md-delim display: inline
This commit is contained in:
+54
-48
@@ -38,8 +38,6 @@ export class Ribbit {
|
||||
api: unknown;
|
||||
element: HTMLElement;
|
||||
states: Record<string, string>;
|
||||
cachedHTML: string | null;
|
||||
cachedMarkdown: string | null;
|
||||
state: string | null;
|
||||
theme: RibbitTheme;
|
||||
themes: ThemeManager;
|
||||
@@ -51,6 +49,12 @@ export class Ribbit {
|
||||
private emitter: RibbitEmitter;
|
||||
private macros: MacroDef[];
|
||||
|
||||
// The markdown source as it existed before view() rendered it to HTML.
|
||||
// Set by subclasses (RibbitEditor) before overwriting element.innerHTML.
|
||||
// Allows getMarkdown() in view state to return the original source rather
|
||||
// than textContent of the rendered HTML (which strips delimiters).
|
||||
protected sourceMarkdown: string | null = null;
|
||||
|
||||
constructor(settings: RibbitSettings) {
|
||||
this.api = settings.api || null;
|
||||
this.element = document.getElementById(settings.editorId || 'ribbit')!;
|
||||
@@ -60,8 +64,6 @@ export class Ribbit {
|
||||
this.states = {
|
||||
VIEW: 'view',
|
||||
};
|
||||
this.cachedHTML = null;
|
||||
this.cachedMarkdown = null;
|
||||
this.state = null;
|
||||
|
||||
this.themes = new ThemeManager(defaultTheme, this.themesPath, (theme, previous) => {
|
||||
@@ -69,7 +71,6 @@ export class Ribbit {
|
||||
this.converter = theme.tags
|
||||
? new HopDown({ tags: theme.tags, macros: this.macros })
|
||||
: new HopDown({ macros: this.macros });
|
||||
this.cachedHTML = null;
|
||||
this.emitter.emit('themeChange', {
|
||||
current: theme,
|
||||
previous,
|
||||
@@ -112,14 +113,13 @@ export class Ribbit {
|
||||
settings.collaboration,
|
||||
{
|
||||
onRemoteUpdate: (content) => {
|
||||
this.cachedMarkdown = content;
|
||||
this.cachedHTML = null;
|
||||
this.sourceMarkdown = content;
|
||||
if (this.getState() !== this.states.VIEW) {
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.element.innerHTML = this.markdownToHTML(content);
|
||||
}
|
||||
this.emitter.emit('change', {
|
||||
markdown: content,
|
||||
html: this.getHTML(),
|
||||
html: this.markdownToHTML(content),
|
||||
});
|
||||
},
|
||||
onPeersChange: (peers) => {
|
||||
@@ -188,7 +188,7 @@ export class Ribbit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Current mode name ('view', 'edit', or 'wysiwyg').
|
||||
* Current mode name ('view' or 'wysiwyg').
|
||||
*
|
||||
* if (editor.getState() === 'wysiwyg') { ... }
|
||||
*/
|
||||
@@ -200,7 +200,7 @@ export class Ribbit {
|
||||
* Transition to a new mode. Updates CSS classes on the editor element
|
||||
* so themes can style each mode differently, and fires modeChange.
|
||||
*
|
||||
* editor.setState('edit');
|
||||
* editor.setState('wysiwyg');
|
||||
*/
|
||||
setState(newState: string): void {
|
||||
const previous = this.state;
|
||||
@@ -225,28 +225,26 @@ export class Ribbit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendered HTML of the current content, cached until invalidated.
|
||||
* Rendered HTML of the current content.
|
||||
*
|
||||
* document.getElementById('preview').innerHTML = viewer.getHTML();
|
||||
*/
|
||||
getHTML(): string {
|
||||
if (this.cachedHTML === null) {
|
||||
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
||||
}
|
||||
return this.cachedHTML;
|
||||
return this.markdownToHTML(this.getMarkdown());
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw markdown of the current content. In view mode this is the
|
||||
* original text; in edit/wysiwyg mode it's derived from the DOM.
|
||||
* Raw markdown of the current content. In view state reads from
|
||||
* sourceMarkdown if set (preserved before rendering overwrote the
|
||||
* element), otherwise falls back to element.textContent.
|
||||
*
|
||||
* fetch('/save', { body: editor.getMarkdown() });
|
||||
*/
|
||||
getMarkdown(): string {
|
||||
if (this.cachedMarkdown === null) {
|
||||
this.cachedMarkdown = this.element.textContent || '';
|
||||
if (this.sourceMarkdown !== null) {
|
||||
return this.sourceMarkdown;
|
||||
}
|
||||
return this.cachedMarkdown;
|
||||
return this.element.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,26 +268,20 @@ export class Ribbit {
|
||||
* editor.view();
|
||||
*/
|
||||
view(): void {
|
||||
if (this.getState() === this.states.VIEW) return;
|
||||
this.invalidateCache();
|
||||
if (this.getState() === this.states.VIEW) {
|
||||
return;
|
||||
}
|
||||
// Capture markdown before overwriting the element with rendered HTML.
|
||||
// getMarkdown() on the base class reads element.textContent when
|
||||
// sourceMarkdown is null — correct for the initial load case where
|
||||
// the element contains raw markdown text.
|
||||
this.sourceMarkdown = this.getMarkdown();
|
||||
this.collaboration?.disconnect();
|
||||
this.element.innerHTML = this.getHTML();
|
||||
this.element.innerHTML = this.markdownToHTML(this.sourceMarkdown);
|
||||
this.setState(this.states.VIEW);
|
||||
this.element.contentEditable = 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Force re-conversion on next getHTML()/getMarkdown() call.
|
||||
* Call after programmatically changing element content.
|
||||
*
|
||||
* editor.element.innerHTML = newContent;
|
||||
* editor.invalidateCache();
|
||||
*/
|
||||
invalidateCache(): void {
|
||||
this.cachedMarkdown = null;
|
||||
this.cachedHTML = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an advisory editing lock. Returns false if another user
|
||||
* holds the lock. Requires a collaboration transport.
|
||||
@@ -297,7 +289,9 @@ export class Ribbit {
|
||||
* if (await editor.lockForEditing()) { editor.wysiwyg(); }
|
||||
*/
|
||||
async lockForEditing(): Promise<boolean> {
|
||||
if (!this.collaboration) return false;
|
||||
if (!this.collaboration) {
|
||||
return false;
|
||||
}
|
||||
return this.collaboration.lock();
|
||||
}
|
||||
|
||||
@@ -318,7 +312,9 @@ export class Ribbit {
|
||||
* await editor.forceLockEditing();
|
||||
*/
|
||||
async forceLockEditing(): Promise<boolean> {
|
||||
if (!this.collaboration) return false;
|
||||
if (!this.collaboration) {
|
||||
return false;
|
||||
}
|
||||
return this.collaboration.forceLock();
|
||||
}
|
||||
|
||||
@@ -329,7 +325,9 @@ export class Ribbit {
|
||||
* revisions.forEach(r => console.log(r.id, r.timestamp));
|
||||
*/
|
||||
async listRevisions(): Promise<Revision[]> {
|
||||
if (!this.collaboration) return [];
|
||||
if (!this.collaboration) {
|
||||
return [];
|
||||
}
|
||||
return this.collaboration.listRevisions();
|
||||
}
|
||||
|
||||
@@ -340,7 +338,9 @@ export class Ribbit {
|
||||
* if (rev) { console.log(rev.content); }
|
||||
*/
|
||||
async getRevision(id: string): Promise<(Revision & { content: string }) | null> {
|
||||
if (!this.collaboration) return null;
|
||||
if (!this.collaboration) {
|
||||
return null;
|
||||
}
|
||||
return this.collaboration.getRevision(id);
|
||||
}
|
||||
|
||||
@@ -351,18 +351,22 @@ export class Ribbit {
|
||||
* await editor.restoreRevision('abc-123');
|
||||
*/
|
||||
async restoreRevision(id: string): Promise<void> {
|
||||
if (!this.collaboration) return;
|
||||
if (!this.collaboration) {
|
||||
return;
|
||||
}
|
||||
const revision = await this.collaboration.getRevision(id);
|
||||
if (!revision) return;
|
||||
this.cachedMarkdown = revision.content;
|
||||
this.cachedHTML = this.markdownToHTML(revision.content);
|
||||
if (!revision) {
|
||||
return;
|
||||
}
|
||||
this.sourceMarkdown = revision.content;
|
||||
const html = this.markdownToHTML(revision.content);
|
||||
this.collaboration.sendUpdate(revision.content);
|
||||
if (this.getState() !== this.states.VIEW) {
|
||||
this.element.innerHTML = this.cachedHTML;
|
||||
this.element.innerHTML = html;
|
||||
}
|
||||
this.emitter.emit('change', {
|
||||
markdown: revision.content,
|
||||
html: this.cachedHTML,
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -373,7 +377,9 @@ export class Ribbit {
|
||||
* const rev = await editor.createRevision({ label: 'v1.0' });
|
||||
*/
|
||||
async createRevision(metadata?: RevisionMetadata): Promise<Revision | null> {
|
||||
if (!this.collaboration) return null;
|
||||
if (!this.collaboration) {
|
||||
return null;
|
||||
}
|
||||
const revision = await this.collaboration.createRevision(this.getMarkdown(), metadata);
|
||||
if (revision) {
|
||||
this.emitter.emit('revisionCreated', { revision });
|
||||
@@ -427,7 +433,7 @@ export function decodeHtmlEntities(html: string): string {
|
||||
/**
|
||||
* Encode characters that would be interpreted as HTML into numeric
|
||||
* entities. Used when displaying raw markdown in contentEditable
|
||||
* (edit mode) so the browser doesn't parse it as markup.
|
||||
* so the browser doesn't parse it as markup.
|
||||
*
|
||||
* encodeHtmlEntities('<b>hi</b>') // '<b>hi</b>'
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user