273 lines
5.9 KiB
Vue
273 lines
5.9 KiB
Vue
<template>
|
|
<!-- eslint-disable vue/no-v-html -->
|
|
<div
|
|
class="module"
|
|
v-if="module.id"
|
|
>
|
|
<div class="module__header">
|
|
<h2
|
|
class="module__meta-title"
|
|
id="meta-title"
|
|
>
|
|
{{ module.metaTitle }}
|
|
</h2>
|
|
|
|
<div
|
|
class="module__categoryindicators"
|
|
v-if="$flavor.showModuleFilter"
|
|
>
|
|
<pill :text="module.level?.name"></pill>
|
|
<pill :text="module.category?.name"></pill>
|
|
</div>
|
|
</div>
|
|
|
|
<h1
|
|
class="module__title"
|
|
data-cy="module-title"
|
|
>
|
|
{{ module.title }}
|
|
</h1>
|
|
<div class="module__hero">
|
|
<img
|
|
:src="module.heroImage"
|
|
alt=""
|
|
class="module__hero-image"
|
|
/>
|
|
<h5
|
|
class="module__hero-source"
|
|
v-if="module.heroSource"
|
|
>
|
|
Quelle: {{ module.heroSource }}
|
|
</h5>
|
|
</div>
|
|
|
|
<div class="module__intro-wrapper">
|
|
<bookmark-actions
|
|
:bookmarked="!!module.bookmark"
|
|
:note="note"
|
|
:edit-mode="module.inEditMode"
|
|
class="module__bookmark-actions"
|
|
data-cy="module-bookmark-actions"
|
|
@add-note="$emit('addNote')"
|
|
@edit-note="$emit('editNote')"
|
|
@bookmark="$emit('bookmark', !module.bookmark)"
|
|
/>
|
|
<div
|
|
class="module__intro intro"
|
|
data-cy="module-intro"
|
|
ref="introDiv"
|
|
v-html="module.intro"
|
|
/>
|
|
</div>
|
|
|
|
<chapter
|
|
:chapter="chapter"
|
|
:index="index"
|
|
:edit-mode="module.inEditMode"
|
|
v-for="(chapter, index) in module.chapters"
|
|
:key="chapter.id"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Chapter from '@/components/Chapter.vue';
|
|
import BookmarkActions from '@/components/notes/BookmarkActions.vue';
|
|
import Pill from '@/components/ui/Pill.vue';
|
|
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';
|
|
|
|
export interface Props {
|
|
module: ModuleNode;
|
|
}
|
|
const props = defineProps<Props>();
|
|
let selectionHandler: SelectionHandlerType;
|
|
|
|
const introDiv = ref<HTMLElement | null>(null);
|
|
|
|
const moduleHighlightsFragment = graphql(`
|
|
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);
|
|
},
|
|
onCreateNote: async (newHighlight: AddHighlightArgument) => {
|
|
// we also open the sidebar when clicking on the note icon
|
|
const highlight = await createHighlight(newHighlight);
|
|
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;
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
@import 'styles/helpers';
|
|
|
|
.module {
|
|
display: flex;
|
|
justify-self: center;
|
|
max-width: 100vw;
|
|
|
|
padding: $large-spacing 0;
|
|
@include desktop {
|
|
width: 800px;
|
|
padding: $large-spacing 15px;
|
|
}
|
|
flex-direction: column;
|
|
-webkit-box-sizing: border-box;
|
|
-moz-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
|
&__hero {
|
|
margin-bottom: 35px;
|
|
}
|
|
|
|
&__hero-image {
|
|
max-width: 100%;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
&__hero-source {
|
|
@include tiny-text;
|
|
line-height: 25px;
|
|
}
|
|
|
|
&__header {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: stretch;
|
|
margin-bottom: $small-spacing;
|
|
}
|
|
|
|
&__meta-title {
|
|
@include meta-title;
|
|
margin-right: $medium-spacing;
|
|
}
|
|
|
|
&__intro-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
&__intro {
|
|
> :deep(p) {
|
|
margin-bottom: $large-spacing;
|
|
@include lead-paragraph;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
> :deep(ul) {
|
|
@include list-parent;
|
|
|
|
> li {
|
|
@include list-child;
|
|
@include lead-paragraph;
|
|
}
|
|
}
|
|
}
|
|
|
|
&__bookmark-actions {
|
|
margin-top: 3px;
|
|
}
|
|
|
|
}
|
|
</style>
|