From f41b43aa701ceef2405d8202371609c21b92b2fa Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 16 Jan 2024 16:53:34 +0100 Subject: [PATCH] Add mutation for adding highlights to client also --- client/src/__generated__/gql.ts | 15 +++++ client/src/__generated__/graphql.ts | 48 ++++++++++++++++ client/src/components/ContentBlock.vue | 77 +++++++++++++++++++++++++- client/src/directives/highlight.ts | 6 +- 4 files changed, 140 insertions(+), 6 deletions(-) diff --git a/client/src/__generated__/gql.ts b/client/src/__generated__/gql.ts index 7fc5bc70..ce01b74a 100644 --- a/client/src/__generated__/gql.ts +++ b/client/src/__generated__/gql.ts @@ -13,6 +13,9 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { + "\n fragment HighlightParts on HighlightNode {\n id\n contentIndex\n paragraphIndex\n selectionLength\n contentUuid\n startPosition\n text\n color\n }\n": types.HighlightPartsFragmentDoc, + "\n fragment ContentBlockHighlightsFragment on ContentBlockNode {\n id\n __typename\n highlights {\n ...HighlightParts\n }\n }\n": types.ContentBlockHighlightsFragmentFragmentDoc, + "\n mutation AddHighlight($input: AddHighlightInput!) {\n addHighlight(input: $input) {\n __typename\n highlight {\n ...HighlightParts\n }\n }\n }\n ": types.AddHighlightDocument, "\n query LanguageQuery {\n me {\n language @client\n }\n }\n ": types.LanguageQueryDocument, "\n mutation SetLanguage($language: String!) {\n setLanguage(language: $language) @client {\n language\n }\n }\n ": types.SetLanguageDocument, "\n query ReadOnlyQuery {\n me {\n readOnly\n selectedClass {\n readOnly\n }\n }\n }\n ": types.ReadOnlyQueryDocument, @@ -47,6 +50,18 @@ const documents = { */ export function graphql(source: string): unknown; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment HighlightParts on HighlightNode {\n id\n contentIndex\n paragraphIndex\n selectionLength\n contentUuid\n startPosition\n text\n color\n }\n"): (typeof documents)["\n fragment HighlightParts on HighlightNode {\n id\n contentIndex\n paragraphIndex\n selectionLength\n contentUuid\n startPosition\n text\n color\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment ContentBlockHighlightsFragment on ContentBlockNode {\n id\n __typename\n highlights {\n ...HighlightParts\n }\n }\n"): (typeof documents)["\n fragment ContentBlockHighlightsFragment on ContentBlockNode {\n id\n __typename\n highlights {\n ...HighlightParts\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation AddHighlight($input: AddHighlightInput!) {\n addHighlight(input: $input) {\n __typename\n highlight {\n ...HighlightParts\n }\n }\n }\n "): (typeof documents)["\n mutation AddHighlight($input: AddHighlightInput!) {\n addHighlight(input: $input) {\n __typename\n highlight {\n ...HighlightParts\n }\n }\n }\n "]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/client/src/__generated__/graphql.ts b/client/src/__generated__/graphql.ts index db2c9688..ae4d8d1a 100644 --- a/client/src/__generated__/graphql.ts +++ b/client/src/__generated__/graphql.ts @@ -68,6 +68,28 @@ export type AddContentBlockPayload = { newContentBlock?: Maybe; }; +export type AddHighlightArgument = { + color: Scalars['String']['input']; + contentBlockId: Scalars['String']['input']; + contentIndex: Scalars['Int']['input']; + contentUuid: Scalars['UUID']['input']; + paragraphIndex: Scalars['Int']['input']; + selectionLength: Scalars['Int']['input']; + startPosition: Scalars['Int']['input']; + text: Scalars['String']['input']; +}; + +export type AddHighlightInput = { + clientMutationId?: InputMaybe; + highlight?: InputMaybe; +}; + +export type AddHighlightPayload = { + __typename?: 'AddHighlightPayload'; + clientMutationId?: Maybe; + highlight?: Maybe; +}; + export type AddModuleRoomEntryArgument = { contents?: InputMaybe>>; roomSlug: Scalars['String']['input']; @@ -1007,6 +1029,7 @@ export type Mutation = { _debug?: Maybe; addComment?: Maybe; addContentBlock?: Maybe; + addHighlight?: Maybe; addModuleRoomEntry?: Maybe; addNote?: Maybe; addObjective?: Maybe; @@ -1078,6 +1101,11 @@ export type MutationAddContentBlockArgs = { }; +export type MutationAddHighlightArgs = { + input: AddHighlightInput; +}; + + export type MutationAddModuleRoomEntryArgs = { input: AddModuleRoomEntryInput; }; @@ -2595,6 +2623,23 @@ export type UserGroupBlockVisibility = { schoolClassId: Scalars['ID']['input']; }; +export type HighlightPartsFragment = { __typename?: 'HighlightNode', id: string, contentIndex: number, paragraphIndex: number, selectionLength: number, contentUuid: any, startPosition: number, text: string, color: string } & { ' $fragmentName'?: 'HighlightPartsFragment' }; + +export type ContentBlockHighlightsFragmentFragment = { __typename: 'ContentBlockNode', id: string, highlights?: Array<( + { __typename?: 'HighlightNode' } + & { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } } + ) | null> | null } & { ' $fragmentName'?: 'ContentBlockHighlightsFragmentFragment' }; + +export type AddHighlightMutationVariables = Exact<{ + input: AddHighlightInput; +}>; + + +export type AddHighlightMutation = { __typename?: 'Mutation', addHighlight?: { __typename: 'AddHighlightPayload', highlight?: ( + { __typename?: 'HighlightNode' } + & { ' $fragmentRefs'?: { 'HighlightPartsFragment': HighlightPartsFragment } } + ) | null } | null }; + export type LanguageQueryQueryVariables = Exact<{ [key: string]: never; }>; @@ -2702,10 +2747,13 @@ export type ModuleSolutionsQueryVariables = Exact<{ export type ModuleSolutionsQuery = { __typename?: 'Query', module?: { __typename?: 'ModuleNode', solutionsEnabled?: boolean | null, slug: string } | null }; +export const HighlightPartsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightParts"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"HighlightNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"contentIndex"}},{"kind":"Field","name":{"kind":"Name","value":"paragraphIndex"}},{"kind":"Field","name":{"kind":"Name","value":"selectionLength"}},{"kind":"Field","name":{"kind":"Name","value":"contentUuid"}},{"kind":"Field","name":{"kind":"Name","value":"startPosition"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}}]} as unknown as DocumentNode; +export const ContentBlockHighlightsFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ContentBlockHighlightsFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ContentBlockNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"highlights"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HighlightParts"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightParts"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"HighlightNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"contentIndex"}},{"kind":"Field","name":{"kind":"Name","value":"paragraphIndex"}},{"kind":"Field","name":{"kind":"Name","value":"selectionLength"}},{"kind":"Field","name":{"kind":"Name","value":"contentUuid"}},{"kind":"Field","name":{"kind":"Name","value":"startPosition"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}}]} as unknown as DocumentNode; export const ModuleLevelFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModuleLevelFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModuleLevelNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"filterAttributeType"}}]}}]} as unknown as DocumentNode; export const SnapshotListItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SnapshotListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SnapshotNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"shared"}}]}}]} as unknown as DocumentNode; export const SnapshotTitleFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SnapshotTitleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SnapshotNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]} as unknown as DocumentNode; export const SnapshotDetailsFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SnapshotDetailsFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SnapshotNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"shared"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"creator"}},{"kind":"Field","name":{"kind":"Name","value":"mine"}}]}}]} as unknown as DocumentNode; +export const AddHighlightDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddHighlight"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AddHighlightInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addHighlight"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"highlight"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"HighlightParts"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HighlightParts"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"HighlightNode"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"contentIndex"}},{"kind":"Field","name":{"kind":"Name","value":"paragraphIndex"}},{"kind":"Field","name":{"kind":"Name","value":"selectionLength"}},{"kind":"Field","name":{"kind":"Name","value":"contentUuid"}},{"kind":"Field","name":{"kind":"Name","value":"startPosition"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}}]} as unknown as DocumentNode; export const LanguageQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LanguageQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"language"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}]}]}}]}}]} as unknown as DocumentNode; export const SetLanguageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetLanguage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"language"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setLanguage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"language"},"value":{"kind":"Variable","name":{"kind":"Name","value":"language"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"language"}}]}}]}}]} as unknown as DocumentNode; export const ReadOnlyQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ReadOnlyQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"selectedClass"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/client/src/components/ContentBlock.vue b/client/src/components/ContentBlock.vue index 2fc21b3a..051a4144 100644 --- a/client/src/components/ContentBlock.vue +++ b/client/src/components/ContentBlock.vue @@ -127,6 +127,8 @@ import type { Modal } from '@/plugins/modal.types'; import { PAGE_LOAD_TIMEOUT } from '@/consts/navigation.consts'; import { getSelectionHandler } from '@/directives/highlight'; +import { graphql } from '@/__generated__'; +import log from 'loglevel'; export interface Props { contentBlock: ExtendedContentBlockNode; @@ -245,7 +247,68 @@ const { mutate: doDeleteContentBlock } = useMutation(DELETE_CONTENT_BLOCK_MUTATI }, })); +graphql(` + fragment HighlightParts on HighlightNode { + id + contentIndex + paragraphIndex + selectionLength + contentUuid + startPosition + text + color + } +`); + +const contentBlockHighlightsFragment = graphql(` + fragment ContentBlockHighlightsFragment on ContentBlockNode { + id + __typename + highlights { + ...HighlightParts + } + } +`); + +const { mutate: doCreateHighlight } = useMutation( + graphql(` + mutation AddHighlight($input: AddHighlightInput!) { + addHighlight(input: $input) { + __typename + highlight { + ...HighlightParts + } + } + } + `), + () => ({ + update: (cache, { data }) => { + const id = cache.identify({ id: props.contentBlock.id, __typename: 'ContentBlockNode' }); + const contentBlockWithHighlights = cache.readFragment({ + id, + fragment: contentBlockHighlightsFragment, + fragmentName: 'ContentBlockHighlightsFragment', + }); + const highlight = data?.addHighlight?.highlight; + + if (highlight) { + cache.writeFragment({ + id, + fragment: contentBlockHighlightsFragment, + fragmentName: 'ContentBlockHighlightsFragment', + data: { + ...contentBlockWithHighlights, + highlights: [...contentBlockWithHighlights.highlights, highlight], + }, + }); + } + }, + }) +); + onMounted(() => { + log.debug('onMounted ContentBlock called'); + const element = contentBlockDiv.value; if (element !== null) { @@ -261,9 +324,17 @@ onMounted(() => { } // add the listener from highlights - element.addEventListener('mouseup', getSelectionHandler(element, props.contentBlock), (newHighlight) => { - console.log(newHighlight); - }); + element.addEventListener( + 'mouseup', + getSelectionHandler(element, props.contentBlock, (newHighlight) => { + console.log(newHighlight); + doCreateHighlight({ + input: { + highlight: newHighlight, + }, + }); + }) + ); } }); diff --git a/client/src/directives/highlight.ts b/client/src/directives/highlight.ts index 4e4cf89f..38c33b8d 100644 --- a/client/src/directives/highlight.ts +++ b/client/src/directives/highlight.ts @@ -17,7 +17,7 @@ import popover from '@/helpers/popover'; export interface Highlight { contentBlock: string; // the id contentIndex: number; // which content of the .contents array do I come from? - contentUUID: string; // the wagtail UUID of the content element + contentUuid: string; // the wagtail UUID of the content element paragraphIndex: number; // which paragraph inside the textBlock do I come from? startPosition: number; // start of the selection selectionLength: number; // length of the selection @@ -125,7 +125,7 @@ export const getSelectionHandler = const highlightedText: Highlight = { contentBlock: contentBlock.id, contentIndex: position, - contentUUID: uuid, + contentUuid: uuid, startPosition: start, paragraphIndex: positionInTextBlock, selectionLength: end - start, @@ -143,6 +143,7 @@ export const getSelectionHandler = onChooseColor: (color: string) => { console.log('chosenColor', color); highlightedText.color = color; + onUpdateHighlight(highlightedText); // const newHighlight: Highlight = { // contentBlock: contentBlock.id, // contentIndex: @@ -152,7 +153,6 @@ export const getSelectionHandler = }) .then(() => { console.log('confirmed here'); - onUpdateHighlight(); }) .catch(() => { console.log('canceled');