Skip to content

Commit 3f8721d

Browse files
Merge pull request #5097 from habibayman/feat/init-RTE
feat(texteditor): initial standalone tiptap editor component
2 parents 273e174 + fd76d2d commit 3f8721d

42 files changed

Lines changed: 3776 additions & 853 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

contentcuration/contentcuration/dev_urls.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.urls import include
99
from django.urls import path
1010
from django.urls import re_path
11+
from django.views.generic import TemplateView
1112
from drf_yasg import openapi
1213
from drf_yasg.views import get_schema_view
1314
from rest_framework import permissions
@@ -75,3 +76,10 @@ def file_server(request, storage_path=None):
7576
re_path(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")),
7677
re_path(r"^content/(?P<storage_path>.+)$", file_server),
7778
]
79+
80+
urlpatterns += [
81+
re_path(
82+
r"^editor-dev(?:/.*)?$",
83+
TemplateView.as_view(template_name="contentcuration/editor_dev.html"),
84+
),
85+
]

contentcuration/contentcuration/frontend/channelEdit/router.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ import ReviewSelectionsPage from './views/ImportFromChannels/ReviewSelectionsPag
1111
import EditModal from './components/edit/EditModal';
1212
import ChannelDetailsModal from 'shared/views/channel/ChannelDetailsModal';
1313
import ChannelModal from 'shared/views/channel/ChannelModal';
14+
import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
1415
import { RouteNames as ChannelRouteNames } from 'frontend/channelList/constants';
1516

1617
const router = new VueRouter({
1718
routes: [
19+
{
20+
path: '/editor-dev',
21+
name: 'TipTapEditorDev',
22+
component: TipTapEditor,
23+
},
1824
{
1925
name: RouteNames.TREE_ROOT_VIEW,
2026
path: '/',

contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,9 @@
277277
},
278278
},
279279
created() {
280-
this.loadContentNodes({ parent__in: [this.rootId] }),
281-
this.loadAncestors({ id: this.nodeId }),
282-
this.loadNodes();
280+
(this.loadContentNodes({ parent__in: [this.rootId] }),
281+
this.loadAncestors({ id: this.nodeId }),
282+
this.loadNodes());
283283
},
284284
mounted() {
285285
this.updateTabTitle(this.$store.getters.appendChannelName(this.$tr('trashModalTitle')));
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Vue from 'vue';
2+
import Vuex from 'vuex';
3+
import VueRouter from 'vue-router';
4+
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
5+
import startApp from 'shared/app';
6+
import storeFactory from 'shared/vuex/baseStore';
7+
8+
Vue.use(VueRouter);
9+
Vue.use(Vuex);
10+
11+
// Create a minimal store that has the required methods
12+
const store = storeFactory();
13+
14+
startApp({
15+
store, // Provide the store - this is required Althought not needed
16+
router: new VueRouter({
17+
routes: [
18+
{
19+
path: '/',
20+
component: TipTapEditor,
21+
},
22+
],
23+
}),
24+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Vue from 'vue';
2+
import Router from 'vue-router';
3+
import TipTapEditor from '../shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
4+
5+
Vue.use(Router);
6+
export default new Router({
7+
mode: 'history',
8+
base: '/editor-dev/',
9+
routes: [{ path: '/', component: TipTapEditor }],
10+
});

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/mathquill/mathquill.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,7 +2794,7 @@ JS environment could actually contain many instances. */
27942794
// skip matching top-level close tag and all tag pairs in between
27952795
var nesting = 1;
27962796
do {
2797-
(i += 1), (token = tokens[i]);
2797+
((i += 1), (token = tokens[i]));
27982798
pray('no missing close tags', token);
27992799
// close tags
28002800
if (token.slice(0, 2) === '</') {
@@ -3919,7 +3919,7 @@ case '!':
39193919
i = 0;
39203920
// FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
39213921
while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
3922-
(str = l.letter + str), (l = l[L]), (i += 1);
3922+
((str = l.letter + str), (l = l[L]), (i += 1));
39233923
}
39243924
// check for an autocommand, going thru substrings longest to shortest
39253925
while (str.length) {
@@ -5039,7 +5039,7 @@ case '!':
50395039
brack.bubble('reflow');
50405040
}
50415041
} else {
5042-
(brack = this), (side = brack.side);
5042+
((brack = this), (side = brack.side));
50435043
if (brack.replacedFragment) brack.side = 0;
50445044
// wrapping seln, don't be one-sided
50455045
else if (cursor[-side]) {
@@ -5279,7 +5279,7 @@ case '!':
52795279
};
52805280
_.parser = function () {
52815281
var self = this;
5282-
(string = Parser.string), (regex = Parser.regex), (succeed = Parser.succeed);
5282+
((string = Parser.string), (regex = Parser.regex), (succeed = Parser.succeed));
52835283
return string('{')
52845284
.then(regex(/^[a-z][a-z0-9]*/i))
52855285
.skip(string('}'))
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<template>
2+
3+
<div class="editor-container">
4+
<EditorToolbar />
5+
<EditorContentWrapper />
6+
</div>
7+
8+
</template>
9+
10+
11+
<script>
12+
13+
import { defineComponent, provide } from 'vue';
14+
import EditorToolbar from './components/EditorToolbar.vue';
15+
import EditorContentWrapper from './components/EditorContentWrapper.vue';
16+
import { useEditor } from './composables/useEditor';
17+
18+
export default defineComponent({
19+
name: 'RichTextEditor',
20+
components: {
21+
EditorToolbar,
22+
EditorContentWrapper,
23+
},
24+
setup() {
25+
const { editor, isReady } = useEditor();
26+
27+
// Provide editor instance to all child components
28+
provide('editor', editor);
29+
provide('isReady', isReady);
30+
},
31+
});
32+
33+
</script>
34+
35+
36+
<style scoped>
37+
38+
.editor-container {
39+
width: 1000px;
40+
margin: 40px auto;
41+
font-family:
42+
'Noto Sans',
43+
-apple-system,
44+
BlinkMacSystemFont,
45+
'Segoe UI',
46+
'Helvetica Neue',
47+
Arial,
48+
sans-serif;
49+
background: white;
50+
border: 1px solid #e1e5e9;
51+
border-radius: 8px;
52+
}
53+
54+
</style>
55+
56+
57+
<!-- Non-scoped styles for typography that apply to editor content -->
58+
<style>
59+
60+
.editor-container h1 {
61+
margin: 24px 0 16px;
62+
font-size: 32px;
63+
font-weight: 600;
64+
}
65+
66+
.editor-container h2 {
67+
margin: 8px 0;
68+
font-size: 24px;
69+
font-weight: 600;
70+
}
71+
72+
.editor-container h3 {
73+
margin: 8px 0;
74+
font-size: 18px;
75+
font-weight: 600;
76+
}
77+
78+
.editor-container p {
79+
margin: 8px 0;
80+
font-size: 16px;
81+
}
82+
83+
.editor-container small {
84+
margin: 4px 0;
85+
font-size: 12px;
86+
}
87+
88+
.editor-container ul,
89+
.editor-container ol {
90+
padding-left: 20px;
91+
margin: 10px 0;
92+
margin-inline-start: 20px;
93+
}
94+
95+
.editor-container li {
96+
margin: 4px 0;
97+
}
98+
99+
</style>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { createTranslator } from 'shared/i18n';
2+
3+
const NAMESPACE = 'TipTapEditorStrings';
4+
5+
const MESSAGES = {
6+
// Toolbar button labels & alt text
7+
undo: {
8+
message: 'Undo',
9+
context: 'Button to undo the last action in the text editor',
10+
},
11+
redo: {
12+
message: 'Redo',
13+
context: 'Button to redo the last undone action in the text editor',
14+
},
15+
bold: {
16+
message: 'Strong',
17+
context: 'Button to make text bold/strong',
18+
},
19+
italic: {
20+
message: 'Italic',
21+
context: 'Button to italicize the selected text',
22+
},
23+
underline: {
24+
message: 'Underline',
25+
context: 'Button to underline the selected text',
26+
},
27+
strikethrough: {
28+
message: 'Strikethrough',
29+
context: 'Button to strikethrough the selected text',
30+
},
31+
copy: {
32+
message: 'Copy',
33+
context: 'Button to copy the selected text',
34+
},
35+
paste: {
36+
message: 'Paste',
37+
context: 'Button to paste text from clipboard',
38+
},
39+
pasteWithoutFormatting: {
40+
message: 'Paste without formatting',
41+
context: 'Button to paste text without any formatting',
42+
},
43+
bulletList: {
44+
message: 'Bullet list',
45+
context: 'Button to create a bullet list',
46+
},
47+
numberedList: {
48+
message: 'Numbered list',
49+
context: 'Button to create a numbered list',
50+
},
51+
subscript: {
52+
message: 'Subscript',
53+
context: 'Button to make text subscript',
54+
},
55+
superscript: {
56+
message: 'Superscript',
57+
context: 'Button to make text superscript',
58+
},
59+
insertImage: {
60+
message: 'Insert image',
61+
context: 'Button to insert an image',
62+
},
63+
insertLink: {
64+
message: 'Insert link',
65+
context: 'Button to insert a hyperlink',
66+
},
67+
mathFormula: {
68+
message: 'Math formula',
69+
context: 'Button to insert a mathematical formula',
70+
},
71+
codeBlock: {
72+
message: 'Code block',
73+
context: 'Button to insert a block of code',
74+
},
75+
76+
// Format dropdown options
77+
formatNormal: {
78+
message: 'Normal',
79+
context: 'Option to set text format to normal',
80+
},
81+
formatSmall: {
82+
message: 'Small',
83+
context: 'Option to set text format to small',
84+
},
85+
formatHeader1: {
86+
message: 'Header 1',
87+
context: 'Option to set text format to header 1',
88+
},
89+
formatHeader2: {
90+
message: 'Header 2',
91+
context: 'Option to set text format to header 2',
92+
},
93+
formatHeader3: {
94+
message: 'Header 3',
95+
context: 'Option to set text format to header 3',
96+
},
97+
98+
// Accessibility labels
99+
textFormattingToolbar: {
100+
message: 'Text formatting toolbar',
101+
context: 'Toolbar for text formatting options',
102+
},
103+
historyActions: {
104+
message: 'History actions',
105+
context: 'Actions for managing history of changes',
106+
},
107+
textFormattingOptions: {
108+
message: 'Text formatting options',
109+
context: 'Options for formatting text',
110+
},
111+
textStyleFormatting: {
112+
message: 'Text style formatting',
113+
context: 'Formatting options for text styles',
114+
},
115+
copyAndPasteActions: {
116+
message: 'Copy and paste actions',
117+
context: 'Actions for copying and pasting text',
118+
},
119+
listFormatting: {
120+
message: 'List formatting',
121+
context: 'Options for formatting lists',
122+
},
123+
scriptFormatting: {
124+
message: 'Script formatting',
125+
context: 'Options for formatting scripts',
126+
},
127+
insertTools: {
128+
message: 'Insert tools',
129+
context: 'Tools for inserting elements into the text',
130+
},
131+
textFormatOptions: {
132+
message: 'Text format options',
133+
context: 'Options for text formatting',
134+
},
135+
formatOptions: {
136+
message: 'Format options',
137+
context: 'General options for formatting',
138+
},
139+
pasteOptions: {
140+
message: 'Paste options',
141+
context: 'Options for pasting text',
142+
},
143+
pasteOptionsMenu: {
144+
message: 'Paste options menu',
145+
context: 'Menu for selecting paste options',
146+
},
147+
clipboardAccessFailed: {
148+
message: 'Clipboard access failed. Try copying again.',
149+
context: 'Message shown when clipboard access fails during paste operation',
150+
},
151+
};
152+
153+
let TipTapEditorStrings = null;
154+
155+
export function getTipTapEditorStrings() {
156+
if (!TipTapEditorStrings) {
157+
TipTapEditorStrings = createTranslator(NAMESPACE, MESSAGES);
158+
}
159+
return TipTapEditorStrings;
160+
}

0 commit comments

Comments
 (0)