Add highlighting to instrument intro

This commit is contained in:
Ramon Wenger 2024-02-29 12:30:52 +01:00
parent 2ee56aa3c0
commit 650f8c05d5
5 changed files with 97 additions and 43 deletions

View File

@ -811,7 +811,7 @@ export type InstrumentNode = Node & {
__typename?: 'InstrumentNode';
bookmarks?: Maybe<Array<Maybe<InstrumentBookmarkNode>>>;
contents?: Maybe<Scalars['GenericStreamFieldType']['output']>;
highlights?: Maybe<Array<Maybe<HighlightNode>>>;
highlights: Array<HighlightNode>;
/** The ID of the object */
id: Scalars['ID']['output'];
intro: Scalars['String']['output'];
@ -2773,7 +2773,7 @@ export type AddContentHighlightMutation = { __typename?: 'Mutation', addContentH
& { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } }
) | null } | null };
export type InstrumentHighlightsWithIdOnlyFragmentFragment = { __typename?: 'InstrumentNode', highlights?: Array<{ __typename?: 'HighlightNode', id: string } | null> | null } & { ' $fragmentName'?: 'InstrumentHighlightsWithIdOnlyFragmentFragment' };
export type InstrumentHighlightsWithIdOnlyFragmentFragment = { __typename?: 'InstrumentNode', highlights: Array<{ __typename?: 'HighlightNode', id: string }> } & { ' $fragmentName'?: 'InstrumentHighlightsWithIdOnlyFragmentFragment' };
export type ContentBlockHighlightsWithIdOnlyFragmentFragment = { __typename?: 'ContentBlockNode', highlights?: Array<{ __typename?: 'HighlightNode', id: string } | null> | null } & { ' $fragmentName'?: 'ContentBlockHighlightsWithIdOnlyFragmentFragment' };
@ -2794,10 +2794,10 @@ export type UpdateContentBookmarkMutation = { __typename?: 'Mutation', updateCon
export type MyActivitiesQueryQueryVariables = Exact<{ [key: string]: never; }>;
export type MyActivitiesQueryQuery = { __typename?: 'Query', myActivities?: { __typename?: 'ActivityNode', instruments: Array<{ __typename?: 'InstrumentNode', id: string, slug: string, title: string, path: string, highlights?: Array<(
export type MyActivitiesQueryQuery = { __typename?: 'Query', myActivities?: { __typename?: 'ActivityNode', instruments: Array<{ __typename?: 'InstrumentNode', id: string, slug: string, title: string, path: string, highlights: Array<(
{ __typename?: 'HighlightNode' }
& { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } }
) | null> | null, bookmarks?: Array<{ __typename?: 'InstrumentBookmarkNode', path: string } | null> | null }>, topics: Array<{ __typename?: 'TopicNode', id: string, title: string, modules?: Array<{ __typename?: 'ModuleNode', id: string, slug: string, title: string, metaTitle: string, myHighlights: Array<(
)>, bookmarks?: Array<{ __typename?: 'InstrumentBookmarkNode', path: string } | null> | null }>, topics: Array<{ __typename?: 'TopicNode', id: string, title: string, modules?: Array<{ __typename?: 'ModuleNode', id: string, slug: string, title: string, metaTitle: string, myHighlights: Array<(
{ __typename?: 'HighlightNode' }
& { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } }
)>, myBookmarks?: Array<{ __typename?: 'ChapterBookmarkNode', path?: string | null, chapter: { __typename?: 'ChapterNode', path?: string | null }, note?: { __typename?: 'NoteNode', id: string, text: string } | null } | { __typename?: 'ContentBlockBookmarkNode', id: string, uuid?: any | null, path?: string | null, contentBlock: { __typename?: 'ContentBlockNode', id: string, path?: string | null }, note?: { __typename?: 'NoteNode', id: string, text: string } | null } | { __typename?: 'InstrumentBookmarkNode' } | { __typename?: 'ModuleBookmarkNode', path?: string | null, note?: { __typename?: 'NoteNode', id: string, text: string } | null } | null> | null, mySubmissions?: Array<{ __typename?: 'StudentSubmissionNode', id: string, text: string, assignment: { __typename?: 'AssignmentNode', id: string, title: string, path: string, module: { __typename?: 'ModuleNode', slug: string } } } | null> | null, myAnswers?: Array<{ __typename?: 'AnswerNode', id: string, survey: { __typename?: 'SurveyNode', path: string, id: string, title: string } } | null> | null }> | null }> } | null };
@ -2816,10 +2816,10 @@ export type ContentBlockQueryQueryVariables = Exact<{
export type ContentBlockQueryQuery = { __typename?: 'Query', contentBlock?: { __typename?: 'ContentBlockNode', path?: string | null } | null };
export type InstrumentPartsFragment = { __typename?: 'InstrumentNode', id: string, title: string, intro: string, slug: string, language?: string | null, contents?: any | null, bookmarks?: Array<{ __typename?: 'InstrumentBookmarkNode', uuid?: any | null, note?: { __typename?: 'NoteNode', id: string, text: string } | null } | null> | null, type?: { __typename?: 'InstrumentTypeNode', id: string, name: string, type: string, category?: { __typename?: 'InstrumentCategoryNode', id: string, name: string, foreground: string, background: string } | null } | null, highlights?: Array<(
export type InstrumentPartsFragment = { __typename?: 'InstrumentNode', id: string, title: string, intro: string, slug: string, language?: string | null, contents?: any | null, bookmarks?: Array<{ __typename?: 'InstrumentBookmarkNode', uuid?: any | null, note?: { __typename?: 'NoteNode', id: string, text: string } | null } | null> | null, type?: { __typename?: 'InstrumentTypeNode', id: string, name: string, type: string, category?: { __typename?: 'InstrumentCategoryNode', id: string, name: string, foreground: string, background: string } | null } | null, highlights: Array<(
{ __typename?: 'HighlightNode' }
& { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } }
) | null> | null } & { ' $fragmentName'?: 'InstrumentPartsFragment' };
)> } & { ' $fragmentName'?: 'InstrumentPartsFragment' };
export type InstrumentQueryQueryVariables = Exact<{
slug: Scalars['String']['input'];
@ -2831,10 +2831,10 @@ export type InstrumentQueryQuery = { __typename?: 'Query', instrument?: (
& { ' $fragmentRefs'?: { 'InstrumentPartsFragment': InstrumentPartsFragment } }
) | null };
export type InstrumentHighlightsFragmentFragment = { __typename: 'InstrumentNode', id: string, slug: string, highlights?: Array<(
export type InstrumentHighlightsFragmentFragment = { __typename: 'InstrumentNode', id: string, slug: string, highlights: Array<(
{ __typename?: 'HighlightNode' }
& { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } }
) | null> | null } & { ' $fragmentName'?: 'InstrumentHighlightsFragmentFragment' };
)> } & { ' $fragmentName'?: 'InstrumentHighlightsFragmentFragment' };
export type MeLanguageQueryVariables = Exact<{ [key: string]: never; }>;

View File

@ -384,7 +384,6 @@ export const deleteHighlightCurry = (highlight: HighlightNode) => () => {
if (success) {
const page = highlight.page;
let fragment: DocumentNode, id;
console.log(page?.__typename);
if (page?.__typename === 'InstrumentNode') {
fragment = graphql(`
fragment InstrumentHighlightsWithIdOnlyFragment on InstrumentNode {

View File

@ -14,6 +14,7 @@
<div
class="instrument__intro intro"
data-cy="instrument-intro"
ref="highlightIntro"
v-html="instrument.intro"
/>
@ -32,17 +33,24 @@
</template>
<script setup lang="ts">
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
import { defineAsyncComponent, nextTick, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { graphql } from '@/__generated__';
import { useQuery } from '@vue/apollo-composable';
import { computed } from '@vue/reactivity';
import { AddHighlightArgument, InstrumentNode } from '@/__generated__/graphql';
import { createHighlightCurry, getSelectionHandler, SelectionHandlerOptions } from '@/helpers/highlight';
import { AddHighlightArgument, HighlightNode, InstrumentNode } from '@/__generated__/graphql';
import {
createHighlightCurry,
getSelectionHandler,
markHighlight,
SelectionHandlerOptions,
SelectionHandlerType,
} from '@/helpers/highlight';
import { doUpdateHighlight } from '@/graphql/mutations';
import highlightSidebar from '@/helpers/highlight-sidebar';
const instrumentDiv = ref<HTMLElement | null>(null);
const highlightIntro = ref<HTMLElement | null>(null);
const ContentComponent = defineAsyncComponent(() => import('@/components/content-blocks/ContentComponent.vue'));
@ -106,46 +114,86 @@ const instrumentHighlightsFragment = graphql(`
}
`);
let selectionHandler;
onResult(() => {
const markHighlights = (highlights: HighlightNode[], element: HTMLElement) => {
for (const highlight of highlights) {
markHighlight(highlight, element, element);
}
};
let contentSelectionHandler: SelectionHandlerType, introSelectionHandler: SelectionHandlerType;
onResult(async () => {
const element = instrumentDiv.value;
const createHighlight = createHighlightCurry({
fragment: instrumentHighlightsFragment,
fragmentName: 'instrumentHighlightsFragment',
cacheSignature: {
slug: instrument.value.slug, // this value is only known onResult
__typename: 'InstrumentNode',
},
const intro = highlightIntro.value;
const fragment = instrumentHighlightsFragment;
const fragmentName = 'instrumentHighlightsFragment';
const cacheSignature = {
slug: instrument.value.slug, // this value is only known onResult
__typename: 'InstrumentNode',
};
const createContentHighlight = createHighlightCurry({
fragment,
fragmentName,
cacheSignature,
isContentHighlight: true,
});
const createIntroHighlight = createHighlightCurry({
fragment,
fragmentName,
cacheSignature,
isContentHighlight: false,
});
const openSidebar = (highlight: HighlightNode) => {
highlightSidebar.open({
highlight,
onUpdateText: (text: string) => {
doUpdateHighlight({
input: {
note: text,
id: highlight.id,
},
});
},
});
};
if (element !== null) {
const options: SelectionHandlerOptions = {
el: element,
page: instrument.value,
const el = element;
const page = instrument.value;
const introOptions: SelectionHandlerOptions = {
el,
page,
onChangeColor: (newHighlight: AddHighlightArgument) => {
createHighlight(newHighlight);
createIntroHighlight(newHighlight);
},
onCreateNote: (newHighlight: AddHighlightArgument) => {
// todo: the same as the other one in ContentBlock.vue, possible to merge
// 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,
},
});
},
});
});
createIntroHighlight(newHighlight).then(openSidebar);
},
parentSelector: 'intro',
};
const contentOptions: SelectionHandlerOptions = {
el,
page,
onChangeColor: (newHighlight: AddHighlightArgument) => {
createContentHighlight(newHighlight);
},
onCreateNote: (newHighlight: AddHighlightArgument) => {
// todo: the same as the other one in ContentBlock.vue, possible to merge
// we also open the sidebar when clicking on the note icon
createContentHighlight(newHighlight).then(openSidebar);
},
};
selectionHandler = getSelectionHandler(options);
element.addEventListener('mouseup', selectionHandler);
contentSelectionHandler = getSelectionHandler(contentOptions);
introSelectionHandler = getSelectionHandler(introOptions);
element.addEventListener('mouseup', contentSelectionHandler);
element.addEventListener('mouseup', introSelectionHandler);
}
if (intro !== null) {
const introHighlights = instrument.value.highlights.filter((h) => h.contentUuid === null);
await nextTick();
markHighlights(introHighlights, intro);
}
});
@ -153,7 +201,8 @@ onUnmounted(() => {
const element = instrumentDiv.value;
if (element !== null) {
element.removeEventListener('mouseup', selectionHandler);
element.removeEventListener('mouseup', contentSelectionHandler);
element.removeEventListener('mouseup', introSelectionHandler);
}
});
</script>
@ -164,6 +213,10 @@ onUnmounted(() => {
.instrument {
padding-top: 2 * $large-spacing;
&__intro {
position: relative;
}
&__title {
font-size: toRem(35px);
margin-bottom: $large-spacing;

View File

@ -44,7 +44,9 @@ class InstrumentNode(DjangoObjectType):
type = graphene.Field(InstrumentTypeNode)
contents = GenericStreamFieldType()
language = graphene.String()
highlights = graphene.List("notes.schema.HighlightNode")
highlights = graphene.List(
graphene.NonNull("notes.schema.HighlightNode"), required=True
)
path = graphene.String(required=True)
class Meta:

View File

@ -450,7 +450,7 @@ type InstrumentNode implements Node {
bookmarks: [InstrumentBookmarkNode]
type: InstrumentTypeNode
language: String
highlights: [HighlightNode]
highlights: [HighlightNode!]!
path: String!
}