From 1d97750632629ea923a6acde14f0a75032e91706 Mon Sep 17 00:00:00 2001 From: habibayman Date: Mon, 11 Aug 2025 06:05:20 +0300 Subject: [PATCH 01/12] refactor(texteditor): remove uploader component from assessment item editor --- .../components/AssessmentItemEditor/AssessmentItemEditor.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue index 788b69e483..8966307d62 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue @@ -205,7 +205,6 @@ openAnswerIdx: null, kindSelectKey: 0, AssessmentItemTypes, - EditorImageProcessor, }; }, computed: { From 079146dffb3be6fcfeb11618fce1567caeb883df Mon Sep 17 00:00:00 2001 From: habibayman Date: Sun, 17 Aug 2025 21:45:26 +0300 Subject: [PATCH 02/12] refactor(texteditor):better DOM handling for image node view --- .../TipTapEditor/components/image/ImageNodeView.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageNodeView.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageNodeView.vue index 7b937e508b..962bc1ae20 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageNodeView.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageNodeView.vue @@ -280,7 +280,8 @@ }); onUnmounted(() => { - clearTimeout(debounceTimer); + // Cancel any pending debounced calls + debouncedSaveSize.cancel(); // Clean up any remaining resize listeners if (resizeListeners) { From 593f20e071c91fd0e60f58a60d9ea85ea49767e0 Mon Sep 17 00:00:00 2001 From: habibayman Date: Sun, 17 Aug 2025 23:23:04 +0300 Subject: [PATCH 03/12] feat(texteditor): swap editor view on touchscreen detect --- .../frontend/shared/utils/browserInfo.js | 11 +++++++++++ .../TipTapEditor/TipTapEditor.vue | 8 +++----- .../TipTapEditor/composables/useBreakpoint.js | 19 ------------------- .../composables/useLinkHandling.js | 5 ++--- .../composables/useModalPositioning.js | 14 ++++++++------ 5 files changed, 24 insertions(+), 33 deletions(-) create mode 100644 contentcuration/contentcuration/frontend/shared/utils/browserInfo.js delete mode 100644 contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useBreakpoint.js diff --git a/contentcuration/contentcuration/frontend/shared/utils/browserInfo.js b/contentcuration/contentcuration/frontend/shared/utils/browserInfo.js new file mode 100644 index 0000000000..ead9c40f42 --- /dev/null +++ b/contentcuration/contentcuration/frontend/shared/utils/browserInfo.js @@ -0,0 +1,11 @@ +/** + * Utility functions to detect browser and device capabilities. + * Currently studio isn't fully buit for touch devices, + * this file should be used for future-proofing + */ + +// Check for presence of the touch event in DOM or multi-touch capabilities +export const isTouchDevice = + 'ontouchstart' in window || + window.navigator?.maxTouchPoints > 0 || + window.navigator?.msMaxTouchPoints > 0; diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue index b01194cdb9..06ab9ce89f 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue @@ -12,7 +12,7 @@ >
@@ -124,8 +124,8 @@ import { preprocessMarkdown } from './utils/markdown'; import MobileTopBar from './components/toolbar/MobileTopBar.vue'; import MobileFormattingBar from './components/toolbar/MobileFormattingBar.vue'; - import { useBreakpoint } from './composables/useBreakpoint'; import { getTipTapEditorStrings } from './TipTapEditorStrings'; + import { isTouchDevice } from 'shared/utils/browserInfo.js'; export default defineComponent({ name: 'RichTextEditor', @@ -154,8 +154,6 @@ ); provide('mathHandler', mathHandler); - const { isMobile } = useBreakpoint(); - const imageHandler = useImageHandling(editor); provide('imageProcessor', props.imageProcessor); @@ -270,7 +268,7 @@ linkHandler, editor, mathHandler, - isMobile, + isTouchDevice, imageHandler, sharedEventHandlers, editorMode: computed(() => props.mode), diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useBreakpoint.js b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useBreakpoint.js deleted file mode 100644 index f750fee6e0..0000000000 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useBreakpoint.js +++ /dev/null @@ -1,19 +0,0 @@ -import { ref, onMounted, onUnmounted } from 'vue'; - -export function useBreakpoint(breakpoint = 768) { - const isMobile = ref(window.innerWidth < breakpoint); - - const onResize = () => { - isMobile.value = window.innerWidth < breakpoint; - }; - - onMounted(() => { - window.addEventListener('resize', onResize); - }); - - onUnmounted(() => { - window.removeEventListener('resize', onResize); - }); - - return { isMobile }; -} diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useLinkHandling.js b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useLinkHandling.js index b774140108..148d8c6e8d 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useLinkHandling.js +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useLinkHandling.js @@ -1,5 +1,5 @@ import { ref, watch, onUnmounted } from 'vue'; -import { useBreakpoint } from './useBreakpoint'; +import { isTouchDevice } from 'shared/utils/browserInfo'; export function useLinkHandling(editor) { const isEditorOpen = ref(false); @@ -9,7 +9,6 @@ export function useLinkHandling(editor) { const editorMode = ref('create'); const savedSelection = ref(null); const isEditorCentered = ref(false); - const { isMobile } = useBreakpoint(); const calculatePosition = (forceCenter = false) => { if (!editor.value) return {}; @@ -17,7 +16,7 @@ export function useLinkHandling(editor) { isEditorCentered.value = false; // Only center the edit modal on mobile, not the bubble menu - if (isMobile.value && forceCenter) { + if (isTouchDevice && forceCenter) { isEditorCentered.value = true; return { position: 'fixed', diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useModalPositioning.js b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useModalPositioning.js index d83f6d703f..9f172e2ad5 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useModalPositioning.js +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/composables/useModalPositioning.js @@ -1,16 +1,18 @@ import { ref, watch } from 'vue'; import { throttle } from 'lodash'; -import { useBreakpoint } from './useBreakpoint'; +import { isTouchDevice } from 'shared/utils/browserInfo'; export function useModalPositioning() { const isModalOpen = ref(false); const popoverStyle = ref({}); const isModalCentered = ref(false); const anchorElement = ref(null); - const { isMobile } = useBreakpoint(); + + let scrollRaf = null; + let resizeRaf = null; const updatePosition = () => { - if (!anchorElement.value || isModalCentered.value || isMobile.value) { + if (!anchorElement.value || isModalCentered.value || isTouchDevice) { return; } const rect = anchorElement.value.getBoundingClientRect(); @@ -25,9 +27,9 @@ export function useModalPositioning() { const handleResize = () => { if (isModalOpen.value) { // Re-evaluate positioning on resize - if (isMobile.value && !isModalCentered.value) { + if (isTouchDevice && !isModalCentered.value) { setCenteredPosition(); - } else if (!isMobile.value && anchorElement.value) { + } else if (!isTouchDevice && anchorElement.value) { updatePosition(); } } @@ -56,7 +58,7 @@ export function useModalPositioning() { const openModal = ({ targetElement = null, centered = false } = {}) => { // Force centered positioning on mobile - if (centered || !targetElement || isMobile.value) { + if (centered || !targetElement || isTouchDevice) { setCenteredPosition(); } else { setAnchoredPosition(targetElement); From 4103d4dc998c78b9ec03de8e3e4bf595d9c67498 Mon Sep 17 00:00:00 2001 From: habibayman Date: Mon, 18 Aug 2025 05:28:19 +0300 Subject: [PATCH 04/12] refactor(texteditor): landscape screens better modals --- .../TipTapEditor/TipTapEditor.vue | 1 + .../components/image/ImageUploadModal.vue | 17 ++++++++ .../components/math/FormulasMenu.vue | 40 ++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue index 06ab9ce89f..d8afaa6469 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue @@ -351,6 +351,7 @@ /* Overlay for edit mode to allow clicking outside to close */ .has-overlay { + z-index: 10; pointer-events: auto; background: rgba(0, 0, 0, 0.5); } diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageUploadModal.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageUploadModal.vue index e79d136de1..f60c840dfc 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageUploadModal.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageUploadModal.vue @@ -536,4 +536,21 @@ outline: 2px solid #0097f2; } + @media screen and (max-height: 500px) and (orientation: landscape) { + .image-upload-modal { + width: 50%; + height: 90%; + max-height: 400px; + overflow-y: auto; + } + + .modal-content { + padding: 1rem; + } + + .image-preview-container { + height: 100px; + } + } + diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/math/FormulasMenu.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/math/FormulasMenu.vue index c018128026..39a4923142 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/math/FormulasMenu.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/math/FormulasMenu.vue @@ -289,6 +289,7 @@ position: relative; width: 90%; max-width: 500px; + max-height: 90vh; } .card { @@ -373,7 +374,7 @@ } .symbols-list { - height: 320px; + height: min(320px, calc(60vh - 180px)); padding: 8px; overflow-y: auto; } @@ -471,4 +472,41 @@ background: #e0e0e0; } + @media screen and (max-height: 500px) and (orientation: landscape) { + .formulas-menu { + width: 95%; + max-width: 600px; + } + + .symbols-list { + height: min(240px, calc(60vh - 120px)); + } + + .symbol-editor math-field { + min-height: 30px; + font-size: 1.1rem; + } + + .info-bar { + min-height: 18px; + margin-top: 0; + font-size: 0.8rem; + } + + .symbol { + min-height: 32px; + padding: 4px; + } + + footer { + height: fit-content; + padding: 0.3rem; + } + + .insert-button { + padding: 4px 8px; + font-size: 0.9rem; + } + } + From bb871dc3986b9dcc02809c6707098ef86a19fe4d Mon Sep 17 00:00:00 2001 From: habibayman Date: Mon, 18 Aug 2025 06:06:44 +0300 Subject: [PATCH 05/12] fix(texteditor):show mobile format toolbar above keyboard --- .../toolbar/MobileFormattingBar.vue | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/toolbar/MobileFormattingBar.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/toolbar/MobileFormattingBar.vue index c63e72cfb4..e6083726a4 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/toolbar/MobileFormattingBar.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/toolbar/MobileFormattingBar.vue @@ -166,13 +166,35 @@ .floating-panel { position: fixed; right: 0; - bottom: 0; + + /* Use visual viewport bottom instead of layout viewport */ + bottom: calc(100vh - 100dvh); left: 0; z-index: 100; display: flex; align-items: center; padding: 0.25rem; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: slide-up 0.2s ease-out; + } + + /* Fallback for older browsers */ + @supports not (height: 100dvh) { + .floating-panel { + bottom: 0; + } + } + + @keyframes slide-up { + from { + opacity: 0; + transform: translateY(100%); + } + + to { + opacity: 1; + transform: translateY(0); + } } .fixed-actions { From 81ed40fa15d3b05744c08671be794bd725b4953c Mon Sep 17 00:00:00 2001 From: habibayman Date: Mon, 18 Aug 2025 07:09:56 +0300 Subject: [PATCH 06/12] feat(texteditor):move ItemToolbar up editor on touchscreens --- .../AnswersEditor/AnswersEditor.vue | 90 ++++++++++++++++++- .../components/HintsEditor/HintsEditor.vue | 45 +++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue index d141d98945..90df4d9070 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue @@ -24,7 +24,92 @@ >
- + + + + + + @@ -131,6 +217,7 @@ import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService'; import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'; + import { isTouchDevice } from 'shared/utils/browserInfo'; const updateAnswersOrder = answers => { return answers.map((answer, idx) => { @@ -174,6 +261,7 @@ EditorImageProcessor, // Make it available in the template correctAnswersIndices: getCorrectAnswersIndices(this.questionKind, this.answers), numericRule: val => floatOrIntRegex.test(val) || this.$tr('numberFieldErrorLabel'), + isTouchDevice, }; }, computed: { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue index 6be4253ed5..c95f510724 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue @@ -23,7 +23,48 @@ :class="hintClasses(hintIdx)" > - + + + + + Date: Sun, 24 Aug 2025 03:33:02 +0300 Subject: [PATCH 07/12] feat(texteditor):move ItemToolbar up on desktop overflow --- .../components/AnswersEditor/AnswersEditor.vue | 9 +++++++-- .../components/HintsEditor/HintsEditor.vue | 11 +++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue index 90df4d9070..14f8afc125 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue @@ -24,8 +24,8 @@ >
- -