Integrate highlighting into module component

This commit is contained in:
Ramon Wenger 2024-02-21 21:57:30 +01:00
parent 092a531d33
commit a7df777b0b
4 changed files with 152 additions and 35 deletions

View File

@ -54,6 +54,8 @@
/> />
<div <div
class="module__intro intro" class="module__intro intro"
data-cy="module-intro"
ref="introDiv"
v-html="module.intro" v-html="module.intro"
/> />
</div> </div>
@ -100,44 +102,146 @@ import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
import Chapter from '@/components/Chapter.vue'; import Chapter from '@/components/Chapter.vue';
import BookmarkActions from '@/components/notes/BookmarkActions.vue'; import BookmarkActions from '@/components/notes/BookmarkActions.vue';
import Pill from '@/components/ui/Pill.vue'; import Pill from '@/components/ui/Pill.vue';
</script> import {
SelectionHandlerType,
SelectionHandlerOptions,
getSelectionHandler,
createHighlightCurry,
markHighlight,
} from '@/helpers/highlight';
import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
import { AddHighlightArgument, HighlightNode, ModuleNode } from '@/__generated__/graphql';
import { graphql } from '@/__generated__';
import highlightSidebar from '@/helpers/highlight-sidebar';
import { doUpdateHighlight } from '@/graphql/mutations';
import Mark from 'mark.js';
import { useRoute } from 'vue-router';
<script lang="ts"> export interface Props {
export default { module: ModuleNode;
props: { }
module: { const route = useRoute();
type: Object, const props = defineProps<Props>();
default: () => ({}), let selectionHandler: SelectionHandlerType;
},
},
computed: { const introDiv = ref<HTMLElement | null>(null);
languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups const moduleHighlightsFragment = graphql(`
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'language_communication') fragment ModuleHighlightsFragment on ModuleNode {
: []; id
__typename
slug
highlights {
...HighlightParts
}
}
`);
const createHighlight = createHighlightCurry({
fragment: moduleHighlightsFragment,
fragmentName: 'ModuleHighlightsFragment',
cacheSignature: { slug: props.module.slug, __typename: 'ModuleNode' },
isContentHighlight: false,
});
onMounted(() => {
const element = introDiv.value;
if (element !== null) {
const options: SelectionHandlerOptions = {
el: element,
page: props.module,
parentSelector: 'module__intro',
onChangeColor: (newHighlight: AddHighlightArgument) => {
createHighlight(newHighlight);
}, },
societyObjectiveGroups() { onCreateNote: (newHighlight: AddHighlightArgument) => {
return this.module.objectiveGroups // we also open the sidebar when clicking on the note icon
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'society') createHighlight(newHighlight).then((highlight) => {
: []; highlightSidebar.open({
highlight,
onUpdateText: (text: string) => {
doUpdateHighlight({
input: {
note: text,
id: highlight.id,
}, },
interdisciplinaryObjectiveGroups() { });
return this.module.objectiveGroups
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'interdisciplinary')
: [];
}, },
note() { });
if (!(this.module && this.module.bookmark)) { });
},
};
selectionHandler = getSelectionHandler(options);
element.addEventListener('mouseup', selectionHandler);
}
markHighlights();
});
const markHighlights = () => {
if (props.module.highlights && introDiv.value) {
for (const highlight of props.module.highlights) {
const highlightNode = highlight as HighlightNode;
const element = introDiv.value.children[highlightNode.paragraphIndex] as HTMLElement;
markHighlight(highlightNode, element, element);
}
}
};
onUnmounted(() => {
const element = introDiv.value;
if (element !== null) {
element.removeEventListener('mouseup', selectionHandler);
}
});
const unmark = () => {
for (const paragraph of introDiv.value.children) {
const instance = new Mark(paragraph);
instance.unmark();
}
};
const highlights = computed(() => {
return props.module ? props.module.highlights : [];
});
watch(
() => highlights.value?.filter((h) => h.color),
() => {
unmark();
markHighlights();
}
);
const note = computed(() => {
if (!(props.module && props.module.bookmark)) {
return; return;
} }
return this.module.bookmark.note; return props.module.bookmark.note;
}, });
showObjectives() {
return this.$route && this.$route.query['show-objectives'] !== undefined; const filterObjectiveGroup = (title: string) => {
}, return props.module.objectiveGroups
}, ? props.module.objectiveGroups.filter((group) => group.title.toLowerCase() === title)
: [];
}; };
const languageCommunicationObjectiveGroups = computed(() => {
return filterObjectiveGroup('language_communication');
});
const societyObjectiveGroups = computed(() => {
return filterObjectiveGroup('society');
});
const interdisciplinaryObjectiveGroups = computed(() => {
return filterObjectiveGroup('interdisciplinary');
});
const showObjectives = computed(() => {
return route && route.query['show-objectives'] !== undefined;
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -22,5 +22,13 @@ fragment HighlightLegacyParts on HighlightNode {
slug slug
id id
} }
... on ChapterNode {
slug
id
}
... on ModuleNode {
slug
id
}
} }
} }

View File

@ -1,3 +1,4 @@
#import "./highlightParts.gql"
fragment ModuleParts on ModuleNode { fragment ModuleParts on ModuleNode {
id id
title title
@ -8,6 +9,9 @@ fragment ModuleParts on ModuleNode {
heroImage heroImage
heroSource heroSource
solutionsEnabled solutionsEnabled
highlights {
...HighlightLegacyParts
}
language language
inEditMode @client inEditMode @client
level { level {

View File

@ -1,6 +1,7 @@
<template> <template>
<module <module
:module="module" :module="module"
v-if="module.id"
@editNote="editNote" @editNote="editNote"
@addNote="addNote" @addNote="addNote"
@bookmark="bookmark" @bookmark="bookmark"