Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0f72f66
config: add tiptap dependency
habibayman Jun 5, 2025
34257da
feat(texteditor): implement static pre tiptap logic editor component
habibayman Jun 6, 2025
d9a6d37
refactor(texteditor): separated tiptap editor to multiple components
habibayman Jun 8, 2025
4048536
feat(texteditor): implement text styling actions
habibayman Jun 8, 2025
1c613c2
feat(texteditor): implement copy/paste actions
habibayman Jun 8, 2025
25bcc53
feat(texteditor): implement lists actions
habibayman Jun 9, 2025
83fa407
feat(texteditor): implement text size formatting actions
habibayman Jun 9, 2025
38741d3
fix(texteditor): disable history buttons when unavailable
habibayman Jun 9, 2025
b21af1c
refactor(texteditor): improve keyboard navigation
habibayman Jun 9, 2025
36f24fa
chore: create dev route view
habibayman Jun 9, 2025
96407d0
feat(texteditor): wrap strings to utilize i18n
habibayman Jun 13, 2025
277925c
fix(texteditor): flip history and lists icons on RTL usage
habibayman Jun 13, 2025
495dd5f
docs(texteditor): add current implementation documentation
habibayman Jun 13, 2025
6eacd1a
fix(texteditor): enforce eslint rules
habibayman Jun 13, 2025
276815c
fix(texteditor): remove white spaces for precommit action
habibayman Jun 13, 2025
dab44fb
fix(texteditor): adjust the editor-dev route to start correctly
habibayman Jun 13, 2025
03a3626
fix(texteditor)[copy]: unexpected extra copying behavior
habibayman Jun 24, 2025
042b719
refactor(texteditor)[i18n]: translated strings syntax
habibayman Jun 24, 2025
230994d
fix(texteditor): enforce eslint rules
habibayman Jun 24, 2025
c25a11c
fix(texteditor)[i18n]: add missing error message translation
habibayman Jun 24, 2025
cb80663
config: bump babel to 7.27.4 to fix merge conflict
habibayman Jun 25, 2025
748f395
config: bump more dependencies to fix conflicts
habibayman Jun 25, 2025
7a3284e
fix(texteditor): enhance copy function
habibayman Jun 25, 2025
3f33063
Merge branch 'unstable' into feat/init-RTE
habibayman Jun 25, 2025
e6f4f2a
docs: update rich_text_editor to include hidden routes
habibayman Jun 25, 2025
c198f14
fix merge conflicts
habibayman Jun 25, 2025
6f7a3ef
Merge branch 'unstable' into feat/init-RTE
habibayman Jun 27, 2025
fd76d2d
fix(texteditor): missing style for non-hot reload
habibayman Jun 30, 2025
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
8 changes: 8 additions & 0 deletions contentcuration/contentcuration/dev_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.urls import include
from django.urls import path
from django.urls import re_path
from django.views.generic import TemplateView
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
Expand Down Expand Up @@ -75,3 +76,10 @@ def file_server(request, storage_path=None):
re_path(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")),
re_path(r"^content/(?P<storage_path>.+)$", file_server),
]

urlpatterns += [
re_path(
r"^editor-dev(?:/.*)?$",
TemplateView.as_view(template_name="contentcuration/editor_dev.html"),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import ReviewSelectionsPage from './views/ImportFromChannels/ReviewSelectionsPag
import EditModal from './components/edit/EditModal';
import ChannelDetailsModal from 'shared/views/channel/ChannelDetailsModal';
import ChannelModal from 'shared/views/channel/ChannelModal';
import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
import { RouteNames as ChannelRouteNames } from 'frontend/channelList/constants';

const router = new VueRouter({
routes: [
{
path: '/editor-dev',
name: 'TipTapEditorDev',
component: TipTapEditor,
},
{
name: RouteNames.TREE_ROOT_VIEW,
path: '/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,9 @@
},
},
created() {
this.loadContentNodes({ parent__in: [this.rootId] }),
this.loadAncestors({ id: this.nodeId }),
this.loadNodes();
(this.loadContentNodes({ parent__in: [this.rootId] }),
this.loadAncestors({ id: this.nodeId }),
this.loadNodes());
},
mounted() {
this.updateTabTitle(this.$store.getters.appendChannelName(this.$tr('trashModalTitle')));
Expand Down
24 changes: 24 additions & 0 deletions contentcuration/contentcuration/frontend/editorDev/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Vue from 'vue';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
import startApp from 'shared/app';
import storeFactory from 'shared/vuex/baseStore';

Vue.use(VueRouter);
Vue.use(Vuex);

// Create a minimal store that has the required methods
const store = storeFactory();

startApp({
store, // Provide the store - this is required Althought not needed
router: new VueRouter({
routes: [
{
path: '/',
component: TipTapEditor,
},
],
}),
});
10 changes: 10 additions & 0 deletions contentcuration/contentcuration/frontend/editorDev/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Vue from 'vue';
import Router from 'vue-router';
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';

Vue.use(Router);
export default new Router({
mode: 'history',
base: '/editor-dev/',
routes: [{ path: '/', component: TipTapEditor }],
});
Original file line number Diff line number Diff line change
Expand Up @@ -2794,7 +2794,7 @@ JS environment could actually contain many instances. */
// skip matching top-level close tag and all tag pairs in between
var nesting = 1;
do {
(i += 1), (token = tokens[i]);
((i += 1), (token = tokens[i]));
pray('no missing close tags', token);
// close tags
if (token.slice(0, 2) === '</') {
Expand Down Expand Up @@ -3919,7 +3919,7 @@ case '!':
i = 0;
// FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
(str = l.letter + str), (l = l[L]), (i += 1);
((str = l.letter + str), (l = l[L]), (i += 1));
}
// check for an autocommand, going thru substrings longest to shortest
while (str.length) {
Expand Down Expand Up @@ -5039,7 +5039,7 @@ case '!':
brack.bubble('reflow');
}
} else {
(brack = this), (side = brack.side);
((brack = this), (side = brack.side));
if (brack.replacedFragment) brack.side = 0;
// wrapping seln, don't be one-sided
else if (cursor[-side]) {
Expand Down Expand Up @@ -5279,7 +5279,7 @@ case '!':
};
_.parser = function () {
var self = this;
(string = Parser.string), (regex = Parser.regex), (succeed = Parser.succeed);
((string = Parser.string), (regex = Parser.regex), (succeed = Parser.succeed));
return string('{')
.then(regex(/^[a-z][a-z0-9]*/i))
.skip(string('}'))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<template>

<div class="editor-container">
<EditorToolbar />
<EditorContentWrapper />
</div>

</template>


<script>

import { defineComponent, provide } from 'vue';
import EditorToolbar from './components/EditorToolbar.vue';
import EditorContentWrapper from './components/EditorContentWrapper.vue';
import { useEditor } from './composables/useEditor';

export default defineComponent({
name: 'RichTextEditor',
components: {
EditorToolbar,
EditorContentWrapper,
},
setup() {
const { editor, isReady } = useEditor();

// Provide editor instance to all child components
provide('editor', editor);
provide('isReady', isReady);
},
});

</script>


<style scoped>

.editor-container {
width: 1000px;
margin: 40px auto;
font-family:
'Noto Sans',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
'Helvetica Neue',
Arial,
sans-serif;
background: white;
border: 1px solid #e1e5e9;
border-radius: 8px;
}

</style>


<!-- Non-scoped styles for typography that apply to editor content -->
<style>

.editor-container h1 {
margin: 24px 0 16px;
font-size: 32px;
font-weight: 600;
}

.editor-container h2 {
margin: 8px 0;
font-size: 24px;
font-weight: 600;
}

.editor-container h3 {
margin: 8px 0;
font-size: 18px;
font-weight: 600;
}

.editor-container p {
margin: 8px 0;
font-size: 16px;
}

.editor-container small {
margin: 4px 0;
font-size: 12px;
}

.editor-container ul,
.editor-container ol {
padding-left: 20px;
margin: 10px 0;
margin-inline-start: 20px;
}

.editor-container li {
margin: 4px 0;
}

</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { createTranslator } from 'shared/i18n';

const NAMESPACE = 'TipTapEditorStrings';

const MESSAGES = {
// Toolbar button labels & alt text
undo: {
message: 'Undo',
context: 'Button to undo the last action in the text editor',
},
redo: {
message: 'Redo',
context: 'Button to redo the last undone action in the text editor',
},
bold: {
message: 'Strong',
context: 'Button to make text bold/strong',
},
italic: {
message: 'Italic',
context: 'Button to italicize the selected text',
},
underline: {
message: 'Underline',
context: 'Button to underline the selected text',
},
strikethrough: {
message: 'Strikethrough',
context: 'Button to strikethrough the selected text',
},
copy: {
message: 'Copy',
context: 'Button to copy the selected text',
},
paste: {
message: 'Paste',
context: 'Button to paste text from clipboard',
},
pasteWithoutFormatting: {
message: 'Paste without formatting',
context: 'Button to paste text without any formatting',
},
bulletList: {
message: 'Bullet list',
context: 'Button to create a bullet list',
},
numberedList: {
message: 'Numbered list',
context: 'Button to create a numbered list',
},
subscript: {
message: 'Subscript',
context: 'Button to make text subscript',
},
superscript: {
message: 'Superscript',
context: 'Button to make text superscript',
},
insertImage: {
message: 'Insert image',
context: 'Button to insert an image',
},
insertLink: {
message: 'Insert link',
context: 'Button to insert a hyperlink',
},
mathFormula: {
message: 'Math formula',
context: 'Button to insert a mathematical formula',
},
codeBlock: {
message: 'Code block',
context: 'Button to insert a block of code',
},

// Format dropdown options
formatNormal: {
message: 'Normal',
context: 'Option to set text format to normal',
},
formatSmall: {
message: 'Small',
context: 'Option to set text format to small',
},
formatHeader1: {
message: 'Header 1',
context: 'Option to set text format to header 1',
},
formatHeader2: {
message: 'Header 2',
context: 'Option to set text format to header 2',
},
formatHeader3: {
message: 'Header 3',
context: 'Option to set text format to header 3',
},

// Accessibility labels
textFormattingToolbar: {
message: 'Text formatting toolbar',
context: 'Toolbar for text formatting options',
},
historyActions: {
message: 'History actions',
context: 'Actions for managing history of changes',
},
textFormattingOptions: {
message: 'Text formatting options',
context: 'Options for formatting text',
},
textStyleFormatting: {
message: 'Text style formatting',
context: 'Formatting options for text styles',
},
copyAndPasteActions: {
message: 'Copy and paste actions',
context: 'Actions for copying and pasting text',
},
listFormatting: {
message: 'List formatting',
context: 'Options for formatting lists',
},
scriptFormatting: {
message: 'Script formatting',
context: 'Options for formatting scripts',
},
insertTools: {
message: 'Insert tools',
context: 'Tools for inserting elements into the text',
},
textFormatOptions: {
message: 'Text format options',
context: 'Options for text formatting',
},
formatOptions: {
message: 'Format options',
context: 'General options for formatting',
},
pasteOptions: {
message: 'Paste options',
context: 'Options for pasting text',
},
pasteOptionsMenu: {
message: 'Paste options menu',
context: 'Menu for selecting paste options',
},
clipboardAccessFailed: {
message: 'Clipboard access failed. Try copying again.',
context: 'Message shown when clipboard access fails during paste operation',
},
};

let TipTapEditorStrings = null;

export function getTipTapEditorStrings() {
if (!TipTapEditorStrings) {
TipTapEditorStrings = createTranslator(NAMESPACE, MESSAGES);
}
return TipTapEditorStrings;
}
Loading