Skip to content

Commit bf90005

Browse files
authored
Merge pull request #5305 from habibayman/feat/RTE-touchscreens
feat(texteditor): swap editor view on touhscreens detection
2 parents 0b06a91 + fae6155 commit bf90005

File tree

14 files changed

+391
-56
lines changed

14 files changed

+391
-56
lines changed

contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,92 @@
2424
>
2525
<div :class="indicatorClasses(answer)"></div>
2626
<VCardText :class="{ 'pb-0': !isAnswerOpen(answerIdx) }">
27-
<VLayout align-top>
27+
<!-- Touch device & desktop layout with toolbar above -->
28+
<template v-if="isTouchDevice || screenSizeLevel <= 3">
29+
<!-- Toolbar row for touch devices -->
30+
<VLayout class="mb-2">
31+
<!-- Selection controls -->
32+
<VFlex shrink>
33+
<VRadioGroup
34+
v-if="shouldHaveOneCorrectAnswer"
35+
:value="correctAnswersIndices"
36+
@change="onCorrectAnswersIndicesUpdate"
37+
>
38+
<VRadio
39+
:value="answerIdx"
40+
data-test="answerRadio"
41+
color="primary"
42+
/>
43+
</VRadioGroup>
44+
45+
<Checkbox
46+
v-if="isMultipleSelection"
47+
:key="answerIdx"
48+
:value="answerIdx"
49+
:inputValue="correctAnswersIndices"
50+
@input="onCorrectAnswersIndicesUpdate"
51+
/>
52+
</VFlex>
53+
54+
<VSpacer />
55+
56+
<!-- Toolbar -->
57+
<VFlex shrink>
58+
<AssessmentItemToolbar
59+
:iconActionsConfig="toolbarIconActions"
60+
:canMoveUp="!isAnswerFirst(answerIdx)"
61+
:canMoveDown="!isAnswerLast(answerIdx)"
62+
class="toolbar"
63+
analyticsLabel="Answer"
64+
data-test="toolbar"
65+
@click="onToolbarClick($event, answerIdx)"
66+
/>
67+
</VFlex>
68+
</VLayout>
69+
70+
<!-- Content row for touch devices -->
71+
<VLayout align-top>
72+
<VFlex xs12>
73+
<!-- Answer content component -->
74+
<keep-alive :max="5">
75+
<div v-if="isInputQuestion">
76+
<VTextField
77+
v-if="isAnswerOpen(answerIdx)"
78+
v-model="answer.answer"
79+
class="answer-number"
80+
type="number"
81+
:rules="[numericRule]"
82+
@change="updateAnswerText($event, answerIdx)"
83+
/>
84+
<VTextField
85+
v-else
86+
:value="answer.answer"
87+
class="no-border"
88+
type="number"
89+
/>
90+
</div>
91+
92+
<div v-else>
93+
<TipTapEditor
94+
v-model="answer.answer"
95+
class="editor"
96+
:mode="isAnswerOpen(answerIdx) ? 'edit' : 'view'"
97+
:imageProcessor="EditorImageProcessor"
98+
@update="updateAnswerText($event, answerIdx)"
99+
@minimize="emitClose"
100+
/>
101+
</div>
102+
</keep-alive>
103+
</VFlex>
104+
</VLayout>
105+
</template>
106+
107+
<!-- Desktop layout -->
108+
<VLayout
109+
v-else
110+
align-top
111+
>
112+
<!-- Selection controls -->
28113
<VFlex shrink>
29114
<!--
30115
VRadio cannot be used without VRadioGroup like VCheckbox but it can
@@ -52,6 +137,7 @@
52137
/>
53138
</VFlex>
54139

140+
<!-- Answer content -->
55141
<VFlex xs10>
56142
<keep-alive :max="5">
57143
<!-- Input question shows a text field with type of `number` -->
@@ -122,6 +208,7 @@
122208

123209
<script>
124210
211+
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
125212
import AssessmentItemToolbar from '../AssessmentItemToolbar';
126213
import { AssessmentItemToolbarActions } from '../../constants';
127214
import { floatOrIntRegex, getCorrectAnswersIndices, mapCorrectAnswers } from '../../utils';
@@ -131,6 +218,7 @@
131218
import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService';
132219
133220
import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
221+
import { isTouchDevice } from 'shared/utils/browserInfo';
134222
135223
const updateAnswersOrder = answers => {
136224
return answers.map((answer, idx) => {
@@ -174,6 +262,7 @@
174262
EditorImageProcessor, // Make it available in the template
175263
correctAnswersIndices: getCorrectAnswersIndices(this.questionKind, this.answers),
176264
numericRule: val => floatOrIntRegex.test(val) || this.$tr('numberFieldErrorLabel'),
265+
isTouchDevice,
177266
};
178267
},
179268
computed: {
@@ -210,6 +299,10 @@
210299
211300
return [];
212301
},
302+
screenSizeLevel() {
303+
const { windowBreakpoint } = useKResponsiveWindow();
304+
return windowBreakpoint.value ?? 0;
305+
},
213306
},
214307
watch: {
215308
answers() {

contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,48 @@
2323
:class="hintClasses(hintIdx)"
2424
>
2525
<VCardText :class="{ 'pt-0 pb-0': !isHintOpen(hintIdx) }">
26-
<VLayout align-top>
26+
<!-- Touch device & desktop layout with toolbar above -->
27+
<template v-if="isTouchDevice || screenSizeLevel <= 3">
28+
<VLayout class="mb-2">
29+
<VFlex
30+
xs1
31+
:style="{ 'margin-top': '10px' }"
32+
>
33+
{{ hintIdx + 1 }}
34+
</VFlex>
35+
<VSpacer />
36+
<VFlex shrink>
37+
<AssessmentItemToolbar
38+
:iconActionsConfig="toolbarIconActions"
39+
:canMoveUp="!isHintFirst(hintIdx)"
40+
:canMoveDown="!isHintLast(hintIdx)"
41+
class="toolbar"
42+
analyticsLabel="Hint"
43+
@click="onToolbarClick($event, hintIdx)"
44+
/>
45+
</VFlex>
46+
</VLayout>
47+
<VLayout>
48+
<VFlex xs12>
49+
<keep-alive :max="5">
50+
<!-- analyticsLabel="Hint"-->
51+
<TipTapEditor
52+
v-model="hint.hint"
53+
:mode="isHintOpen(hintIdx) ? 'edit' : 'view'"
54+
:image-processor="EditorImageProcessor"
55+
@update="updateHintText($event, hintIdx)"
56+
@minimize="emitClose"
57+
/>
58+
</keep-alive>
59+
</VFlex>
60+
</VLayout>
61+
</template>
62+
63+
<!-- Desktop layout -->
64+
<VLayout
65+
v-else
66+
align-top
67+
>
2768
<VFlex
2869
xs1
2970
:style="{ 'margin-top': '10px' }"
@@ -80,10 +121,12 @@
80121

81122
<script>
82123
124+
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
83125
import AssessmentItemToolbar from '../AssessmentItemToolbar';
84126
import { AssessmentItemToolbarActions } from '../../constants';
85127
import { swapElements } from 'shared/utils/helpers';
86128
import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService';
129+
import { isTouchDevice } from 'shared/utils/browserInfo';
87130
88131
import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
89132
@@ -124,8 +167,15 @@
124167
AssessmentItemToolbarActions.DELETE_ITEM,
125168
],
126169
EditorImageProcessor,
170+
isTouchDevice,
127171
};
128172
},
173+
computed: {
174+
screenSizeLevel() {
175+
const { windowBreakpoint } = useKResponsiveWindow();
176+
return windowBreakpoint.value ?? 0;
177+
},
178+
},
129179
methods: {
130180
emitOpen(hintIdx) {
131181
this.$emit('open', hintIdx);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Utility functions to detect browser and device capabilities.
3+
* Currently studio isn't fully buit for touch devices,
4+
* this file should be used for future-proofing
5+
*/
6+
7+
// Check for presence of the touch event in DOM or multi-touch capabilities
8+
export const isTouchDevice =
9+
'ontouchstart' in window ||
10+
window.navigator?.maxTouchPoints > 0 ||
11+
window.navigator?.msMaxTouchPoints > 0;

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
>
1313
<div v-if="editorMode === 'edit'">
1414
<EditorToolbar
15-
v-if="!isMobile"
15+
v-if="!isTouchDevice"
1616
v-on="sharedEventHandlers"
1717
@minimize="emitMinimize"
1818
/>
@@ -124,8 +124,8 @@
124124
import { preprocessMarkdown } from './utils/markdown';
125125
import MobileTopBar from './components/toolbar/MobileTopBar.vue';
126126
import MobileFormattingBar from './components/toolbar/MobileFormattingBar.vue';
127-
import { useBreakpoint } from './composables/useBreakpoint';
128127
import { getTipTapEditorStrings } from './TipTapEditorStrings';
128+
import { isTouchDevice } from 'shared/utils/browserInfo.js';
129129
130130
export default defineComponent({
131131
name: 'RichTextEditor',
@@ -154,8 +154,6 @@
154154
);
155155
provide('mathHandler', mathHandler);
156156
157-
const { isMobile } = useBreakpoint();
158-
159157
const imageHandler = useImageHandling(editor);
160158
provide('imageProcessor', props.imageProcessor);
161159
@@ -270,7 +268,7 @@
270268
linkHandler,
271269
editor,
272270
mathHandler,
273-
isMobile,
271+
isTouchDevice,
274272
imageHandler,
275273
sharedEventHandlers,
276274
editorMode: computed(() => props.mode),
@@ -353,6 +351,7 @@
353351
354352
/* Overlay for edit mode to allow clicking outside to close */
355353
.has-overlay {
354+
z-index: 10;
356355
pointer-events: auto;
357356
background: rgba(0, 0, 0, 0.5);
358357
}

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditorStrings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ const MESSAGES = {
7676
message: 'Code block',
7777
context: 'Button to insert a block of code',
7878
},
79+
moreButtonText: {
80+
message: 'More',
81+
context: 'Label for the "More" dropdown button in the toolbar',
82+
},
7983

8084
// Format dropdown options
8185
formatNormal: {

0 commit comments

Comments
 (0)