Add highlighting to instrument intro
This commit is contained in:
parent
2ee56aa3c0
commit
650f8c05d5
|
|
@ -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; }>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -450,7 +450,7 @@ type InstrumentNode implements Node {
|
|||
bookmarks: [InstrumentBookmarkNode]
|
||||
type: InstrumentTypeNode
|
||||
language: String
|
||||
highlights: [HighlightNode]
|
||||
highlights: [HighlightNode!]!
|
||||
path: String!
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue