Integrate highlighting into module component
This commit is contained in:
parent
092a531d33
commit
a7df777b0b
|
|
@ -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
|
||||||
societyObjectiveGroups() {
|
slug
|
||||||
return this.module.objectiveGroups
|
highlights {
|
||||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'society')
|
...HighlightParts
|
||||||
: [];
|
}
|
||||||
},
|
}
|
||||||
interdisciplinaryObjectiveGroups() {
|
`);
|
||||||
return this.module.objectiveGroups
|
|
||||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'interdisciplinary')
|
const createHighlight = createHighlightCurry({
|
||||||
: [];
|
fragment: moduleHighlightsFragment,
|
||||||
},
|
fragmentName: 'ModuleHighlightsFragment',
|
||||||
note() {
|
cacheSignature: { slug: props.module.slug, __typename: 'ModuleNode' },
|
||||||
if (!(this.module && this.module.bookmark)) {
|
isContentHighlight: false,
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
return this.module.bookmark.note;
|
onMounted(() => {
|
||||||
},
|
const element = introDiv.value;
|
||||||
showObjectives() {
|
if (element !== null) {
|
||||||
return this.$route && this.$route.query['show-objectives'] !== undefined;
|
const options: SelectionHandlerOptions = {
|
||||||
},
|
el: element,
|
||||||
},
|
page: props.module,
|
||||||
|
parentSelector: 'module__intro',
|
||||||
|
onChangeColor: (newHighlight: AddHighlightArgument) => {
|
||||||
|
createHighlight(newHighlight);
|
||||||
|
},
|
||||||
|
onCreateNote: (newHighlight: AddHighlightArgument) => {
|
||||||
|
// we also open the sidebar when clicking on the note icon
|
||||||
|
createHighlight(newHighlight).then((highlight) => {
|
||||||
|
highlightSidebar.open({
|
||||||
|
highlight,
|
||||||
|
onUpdateText: (text: string) => {
|
||||||
|
doUpdateHighlight({
|
||||||
|
input: {
|
||||||
|
note: text,
|
||||||
|
id: highlight.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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 props.module.bookmark.note;
|
||||||
|
});
|
||||||
|
|
||||||
|
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">
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,13 @@ fragment HighlightLegacyParts on HighlightNode {
|
||||||
slug
|
slug
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
... on ChapterNode {
|
||||||
|
slug
|
||||||
|
id
|
||||||
|
}
|
||||||
|
... on ModuleNode {
|
||||||
|
slug
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue