diff --git a/client/cypress/e2e/frontend/modules/highlights.cy.ts b/client/cypress/e2e/frontend/modules/highlights.cy.ts index 2eac3565..5fee435d 100644 --- a/client/cypress/e2e/frontend/modules/highlights.cy.ts +++ b/client/cypress/e2e/frontend/modules/highlights.cy.ts @@ -1,4 +1,5 @@ import { defaultModuleQueriesandMutations } from '../../../support/helpers'; +import { instrumentWithLongContent } from './instrument-highlights'; const getModule = (contents) => { return { @@ -431,4 +432,24 @@ describe('Highlights', () => { updateHighlight(highlightedText); deleteHighlight(); }); + + it('visits an instrument with long content and highlights some text', () => { + cy.mockGraphqlOps({ + operations: { + ...operations, + AddContentHighlight: getAddContentHighlight('InstrumentNode'), + InstrumentQuery: { + instrument: instrumentWithLongContent, + }, + }, + }); + cy.visit(`/instrument/${instrumentSlug}`); + cy.wait('@InstrumentQuery'); + markText(); + const highlightedText = 'nn eine Pe'; + createHighlight(highlightedText, true); + updateHighlight(highlightedText); + addNote(); + deleteHighlight(); + }); }); diff --git a/client/cypress/e2e/frontend/modules/instrument-highlights.ts b/client/cypress/e2e/frontend/modules/instrument-highlights.ts new file mode 100644 index 00000000..a2bd5099 --- /dev/null +++ b/client/cypress/e2e/frontend/modules/instrument-highlights.ts @@ -0,0 +1,74 @@ +export const instrumentWithLongContent = { + id: 'SW5zdHJ1bWVudE5vZGU6Nzkz', + title: 'Handlungsfähigkeit und Urteilsfähigkeit', + intro: '', + slug: 'my-instrument', + language: 'de', + bookmarks: [], + type: { + id: 'SW5zdHJ1bWVudFR5cGVOb2RlOjIy', + name: 'Recht', + category: { + id: 'SW5zdHJ1bWVudENhdGVnb3J5Tm9kZToy', + name: 'Gesellschaft', + foreground: '#0F7CAC', + background: '#DBEEF6', + __typename: 'InstrumentCategoryNode', + }, + type: 'recht', + __typename: 'InstrumentTypeNode', + }, + contents: [ + { + id: '7a2783cf-85b9-48bd-bc63-7ebd58da0816', + type: 'section_title', + value: { + text: 'Handlungsfähigkeit', + }, + }, + { + id: 'ec0810ef-2800-4ba5-a70f-d81d4703fa13', + type: 'text_block', + value: { + text: '

Wenn eine Person etwas kauft oder mietet, dann geht sie ein Rechtsverhältnis mit einer anderen Vertragspartei (Verkäuferin oder Vermieter) ein. Berechtigt dazu sind alle Menschen, die volljährig und urteilsfähig sind. Sie sind rechtlich gesehen voll handlungsfähig. Aber nicht alle Menschen, die etwas kaufen oder mieten möchten, erfüllen diese Voraussetzungen. Wir unterscheiden deshalb folgende Abstufungen:

', + }, + }, + { + id: '69107c06-e315-4bdc-85d2-b5529932130b', + type: 'section_title', + value: { + text: 'Urteilsfähigkeit', + }, + }, + { + id: 'ed9fe5fd-b4e9-4fc9-a50b-5fc3fd92e3a2', + type: 'text_block', + value: { + text: '

Wenn wir die Tabelle zu den Abstufungen der Handlungsfähigkeit betrachten, dann fällt etwas auf: Nur wer in einer bestimmten Situation urteilsfähig ist, kann etwas kaufen oder verkaufen, etwas mieten oder vermieten usw. Wenn jemand in einer bestimmten Situation nicht urteilsfähig ist, dann kann diese Person (fast) keine Handlungen vornehmen, die rechtlich verbindlich sind.

Da die Urteilsfähigkeit also die Voraussetzung für sämtliche rechtliche Handlungen ist, schauen wir uns die Voraussetzungen (= Bedingungen) der Urteilsfähigkeit genauer an:

', + }, + }, + { + id: '50a3f4fc-c150-4450-bcf4-57ee11ff3462', + type: 'text_block', + value: { + text: '

Urteilsfähig ist eine Person, wenn sie bezüglich einer Rechtshandlung über eine recht grosse Erfahrung verfügt und die Folgen dieser Handlung eher gering sind.

Nicht urteilsfähig ist eine Person, wenn sie bezüglich einer Rechtshandlung über eine geringe Erfahrung verfügt und die Folgen dieser Handlung eher gross sind.

', + }, + }, + { + id: 'b5fded29-070d-41d7-88df-db1e9d7757e1', + type: 'subtitle', + value: { + text: 'Was bedeutet das für Jugendliche?', + }, + }, + { + id: '180535d7-b179-48c9-b8b2-522491ff12ca', + type: 'text_block', + value: { + text: '

Ganz oben haben wir festgestellt, dass man eigentlich voll handlungsfähig sein muss, um einen rechtsgültigen Vertrag abzuschliessen. Aber offenbar gibt es auch Situationen, in denen gerade Jugendliche etwas kaufen oder mieten können. Dies ist in zwei Fällen möglich:

Fall 1: Die minderjährige Person ist urteilsfähig und es liegt eine Einwilligung der gesetzlichen Vertreter (Eltern, Beistand usw., auch nachträglich möglich) vor.

Fall 2: Wenn die gesetzlichen Vertreter nicht einwilligen: Minderjährige Person ist urteilsfähig und sie verfügt aber über die nötigen finanziellen Mittel (Taschengeld, Lohn, Geschenke), um die Sache zu bezahlen.

', + }, + }, + ], + highlights: [], + __typename: 'InstrumentNode', +}; diff --git a/client/src/graphql/cache.ts b/client/src/graphql/cache.ts index 367b6eb6..4195c5de 100644 --- a/client/src/graphql/cache.ts +++ b/client/src/graphql/cache.ts @@ -25,6 +25,13 @@ const typePolicies = { }, InstrumentNode: { keyFields: ['slug'], + fields: { + highlights: { + merge(_existing: HighlightNode[], incoming: HighlightNode[]) { + return incoming; + }, + }, + }, }, RoomNode: { keyFields: ['slug'], diff --git a/client/src/helpers/highlight.ts b/client/src/helpers/highlight.ts index 3eb228d4..71b6196d 100644 --- a/client/src/helpers/highlight.ts +++ b/client/src/helpers/highlight.ts @@ -40,6 +40,14 @@ const isInsideViewport = (position: number) => { return position >= TOP_TRESHOLD && position <= BOTTOM_TRESHOLD; }; +const isVisibleInViewport = (rect: DOMRect) => { + return ( + isInsideViewport(rect.top) || // top of element is inside viewport + isInsideViewport(rect.bottom) || // bottom of element is inside viewport + (rect.top <= TOP_TRESHOLD && rect.bottom >= BOTTOM_TRESHOLD) // element spans whole viewport, starts before and ends after + ); +}; + const findClosestAncestorWithTag = (node: Node, tags: string | string[]): HTMLElement | null => { let tagList = tags; if (!Array.isArray(tagList)) { @@ -142,7 +150,7 @@ export const getSelectionHandler = // (el: HTMLElement, contentBlock: ContentBlockNode, onUpdateHighlight: (highlight: any) => void = () => {}) => (_e: Event) => { const rect = el.getBoundingClientRect(); - if (isInsideViewport(rect.top) || isInsideViewport(rect.bottom)) { + if (isVisibleInViewport(rect)) { // the listener only does something if the `el` is visible in the viewport, to save resources // now we check if the selection is inside our container const selection = rangy.getSelection();