Skip to content
Merged
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
4 changes: 2 additions & 2 deletions contentcuration/contentcuration/frontend/editorDev/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
import DevHarness from '../shared/views/TipTapEditor/TipTapEditor/DevHarness.vue';
import startApp from 'shared/app';
import storeFactory from 'shared/vuex/baseStore';

Expand All @@ -17,7 +17,7 @@ startApp({
routes: [
{
path: '/',
component: TipTapEditor,
component: DevHarness,
},
],
}),
Expand Down
4 changes: 2 additions & 2 deletions contentcuration/contentcuration/frontend/editorDev/router.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Vue from 'vue';
import Router from 'vue-router';
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
import DevHarness from '../shared/views/TipTapEditor/TipTapEditor/DevHarness.vue';

Vue.use(Router);
export default new Router({
mode: 'history',
base: '/editor-dev/',
routes: [{ path: '/', component: TipTapEditor }],
routes: [{ path: '/', component: DevHarness }],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<template>

<div class="dev-harness">
<header class="harness-header">
<h1>TipTap Editor - Development Harness</h1>
<p>This page simulates a parent component to test the editor in isolation.</p>
</header>
<hr >
<TipTapEditor v-model="markdownContent" />
<hr >
<div class="raw-output-wrapper">
<h2>Live Markdown Output (v-model state)</h2>
<pre>{{ markdownContent }}</pre>
</div>
</div>

</template>


<script>

import { defineComponent, ref } from 'vue';
import TipTapEditor from './TipTapEditor.vue';

const SAMPLE_MARKDOWN = `# Testing basic & special nodes

**bold** *italic* <u>underline</u> ~~strikethrough~~

try inline formulas $$x^2$$ test

- list a
- list b

<small class="small-text">
small text
</small>

1. list one<sub>[1]</sub>
2. list two

There is a [link here](https://github.com/learningequality/studio/pull/5155/checks)!

\`\`\`javascript
export default html => {
const domParser = new DOMParser();
const doc = domParser.parseFromString(html, 'text/html');
const mdImages = doc.querySelectorAll('span[is="markdown-image-field"]');

for (const mdImageEl of mdImages) {
mdImageEl.replaceWith(mdImageEl.innerHTML.trim());
}

const editOptionButtons = doc.querySelectorAll('.ignore-md');
for (const editOptionsEl of editOptionButtons) {
editOptionsEl.remove();
}
return doc.body.innerHTML;
};
\`\`\`
`;

export default defineComponent({
name: 'DevHarness',
components: {
TipTapEditor,
},
setup() {
// simulates the state of the parent component
const markdownContent = ref(SAMPLE_MARKDOWN);
return { markdownContent };
},
});

</script>


<style scoped>

.dev-harness {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}

.harness-header {
margin: 20px;
text-align: center;
}

.raw-output-wrapper {
width: 1000px;
padding: 1rem;
margin-top: 20px;
color: #f8f8f2;
background-color: #2d2d2d;
border-radius: 8px;
}

.raw-output-wrapper h2 {
margin-top: 0;
color: #ffffff;
}

pre {
word-wrap: break-word;
white-space: pre-wrap;
}

</style>
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@

<script>

import { defineComponent, provide, onMounted } from 'vue';
import { defineComponent, provide, watch, nextTick } from 'vue';
import EditorToolbar from './components/EditorToolbar.vue';
import EditorContentWrapper from './components/EditorContentWrapper.vue';
import { useEditor } from './composables/useEditor';
Expand All @@ -85,6 +85,7 @@
import LinkEditor from './components/link/LinkEditor.vue';
import { useMathHandling } from './composables/useMathHandling';
import FormulasMenu from './components/math/FormulasMenu.vue';
import { preprocessMarkdown } from './utils/markdown';

export default defineComponent({
name: 'RichTextEditor',
Expand All @@ -96,7 +97,7 @@
LinkEditor,
FormulasMenu,
},
setup() {
setup(props, { emit }) {
const { editor, isReady, initializeEditor } = useEditor();
provide('editor', editor);
provide('isReady', isReady);
Expand All @@ -107,10 +108,6 @@
const mathHandler = useMathHandling(editor);
provide('mathHandler', mathHandler);

onMounted(() => {
initializeEditor();
});

const {
modalMode,
modalInitialData,
Expand All @@ -130,6 +127,59 @@
}
};

const getMarkdownContent = () => {
if (!editor.value || !isReady.value || !editor.value.storage?.markdown) {
return '';
}
return editor.value.storage.markdown.getMarkdown();
};

let isUpdatingFromOutside = false; // A flag to prevent infinite update loops

// sync changes from the parent component to the editor
watch(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case for the parent component changing the content? Could/would this happen while I'm actively editing the content?

Copy link
Copy Markdown
Member Author

@habibayman habibayman Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently no, it's not used anywhere.
I wrote it because my goal was to mimic a similar communication way to the one the old editor had with its parent.
The old editor didn't use V-bind but the data inside it was passed in both:

  • Down(Parent to Editor)
    The editor listens for changes on the markdown from the parent.
    Check this piece of code.

  • Up(Editor to parent)
    Native library event (editor.on('change', ...)) -> Vue event (this.$emit('change', ...)) -> Parent handler (@change="...")

I recreated the same logic using a different pattern,
I believe the "communication" method part finalization belongs more to the editor replacement issue #5206
so we'll be talking more about this in that PR giving it a closer look.

() => props.value,
newValue => {
const processedContent = preprocessMarkdown(newValue);

if (!editor.value) {
initializeEditor(processedContent);
return;
}

const editorContent = getMarkdownContent();
if (editorContent !== newValue) {
isUpdatingFromOutside = true;
editor.value.commands.setContent(processedContent, false);
nextTick(() => {
isUpdatingFromOutside = false;
});
}
},
{ immediate: true },
);

// sync changes from the editor to the parent component
watch(
() => editor.value?.state,
() => {
if (
!editor.value ||
!isReady.value ||
isUpdatingFromOutside ||
!editor.value.storage?.markdown
) {
return;
}

const markdown = getMarkdownContent();
if (markdown !== props.value) {
emit('input', markdown);
}
},
{ deep: true },
);

return {
isReady,
modalMode,
Expand All @@ -147,6 +197,13 @@
mathHandler,
};
},
props: {
value: {
type: String,
default: '',
},
},
emits: ['input'],
});

</script>
Expand Down
Loading
Loading