diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ac2e1770..955392c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0 - `{%speakerdeck foobar %}` -> Embedding removed - `{%pdf https://example.org/example-pdf.pdf %}` -> Embedding removed - The use of `sequence` as code block language ([Why?](https://hedgedoc.org/faq/)) -- Comma-separated definition of tags in the yaml-metadata +- Comma-separated definition of tags in the yaml-frontmatter ### Removed diff --git a/cypress/integration/autocompletion.spec.ts b/cypress/integration/autocompletion.spec.ts index 519423015..37e79e83a 100644 --- a/cypress/integration/autocompletion.spec.ts +++ b/cypress/integration/autocompletion.spec.ts @@ -6,7 +6,7 @@ describe('Autocompletion', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() cy.get('.CodeMirror') .click() .get('textarea') diff --git a/cypress/integration/documentTitle.spec.ts b/cypress/integration/documentTitle.spec.ts index cc9fb596c..82c2292f6 100644 --- a/cypress/integration/documentTitle.spec.ts +++ b/cypress/integration/documentTitle.spec.ts @@ -9,7 +9,7 @@ import { branding } from '../support/config' const title = 'This is a test title' describe('Document Title', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() cy.get('.btn.active.btn-outline-secondary > i.fa-columns') .should('exist') }) diff --git a/cypress/integration/editorMode.spec.ts b/cypress/integration/editorMode.spec.ts index 4574e1b2f..906b2d583 100644 --- a/cypress/integration/editorMode.spec.ts +++ b/cypress/integration/editorMode.spec.ts @@ -6,14 +6,14 @@ describe('Editor mode from URL parameter is used', () => { it('mode view', () => { - cy.visit('/n/features?view') + cy.visitTestEditor('view') cy.get('.splitter.left') .should('have.class', 'd-none') cy.get('.splitter.right') .should('not.have.class', 'd-none') }) it('mode both', () => { - cy.visit('/n/features?both') + cy.visitTestEditor('both') cy.get('.splitter.left') .should('not.have.class', 'd-none') cy.get('.splitter.separator') @@ -22,7 +22,7 @@ describe('Editor mode from URL parameter is used', () => { .should('not.have.class', 'd-none') }) it('mode edit', () => { - cy.visit('/n/features?edit') + cy.visitTestEditor('edit') cy.get('.splitter.left') .should('not.have.class', 'd-none') cy.get('.splitter.right') diff --git a/cypress/integration/export.spec.ts b/cypress/integration/export.spec.ts index 6260e2708..819fd25f7 100644 --- a/cypress/integration/export.spec.ts +++ b/cypress/integration/export.spec.ts @@ -9,7 +9,7 @@ describe('Export', () => { const testContent = `---\ntitle: ${testTitle}\n---\nThis is some test content` beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() cy.codemirrorFill(testContent) }) diff --git a/cypress/integration/fileUpload.spec.ts b/cypress/integration/fileUpload.spec.ts index 80d2f0df7..67dc6589b 100644 --- a/cypress/integration/fileUpload.spec.ts +++ b/cypress/integration/fileUpload.spec.ts @@ -8,7 +8,7 @@ const imageUrl = 'http://example.com/non-existing.png' describe('File upload', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() }) it('doesn\'t prevent drag\'n\'drop of plain text', () => { diff --git a/cypress/integration/helpDialog.spec.ts b/cypress/integration/helpDialog.spec.ts index d9dce7561..1c9c73548 100644 --- a/cypress/integration/helpDialog.spec.ts +++ b/cypress/integration/helpDialog.spec.ts @@ -6,7 +6,7 @@ describe('Help Dialog', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() }) it('ToDo-List', () => { diff --git a/cypress/integration/highlightedCodeBlock.spec.ts b/cypress/integration/highlightedCodeBlock.spec.ts index 96834c850..14d6034c2 100644 --- a/cypress/integration/highlightedCodeBlock.spec.ts +++ b/cypress/integration/highlightedCodeBlock.spec.ts @@ -12,7 +12,7 @@ const findHljsCodeBlock = () => { describe('Code', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() }) describe('with just the language', () => { diff --git a/cypress/integration/import.spec.ts b/cypress/integration/import.spec.ts index f7d5964c5..df75f90de 100644 --- a/cypress/integration/import.spec.ts +++ b/cypress/integration/import.spec.ts @@ -6,7 +6,7 @@ describe('Import markdown file', () => { beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() }) it('import on blank note', () => { diff --git a/cypress/integration/maxLength.spec.ts b/cypress/integration/maxLength.spec.ts index abe3b4ce1..2e1108f64 100644 --- a/cypress/integration/maxLength.spec.ts +++ b/cypress/integration/maxLength.spec.ts @@ -10,7 +10,7 @@ describe('The status bar text length info', () => { const tooMuchTestContent = `${dangerTestContent}a` beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() }) it('shows the maximal length of the document as number of available characters in the tooltip', () => { diff --git a/cypress/integration/toolbar.spec.ts b/cypress/integration/toolbar.spec.ts index 3ad8aaf19..631b2cf01 100644 --- a/cypress/integration/toolbar.spec.ts +++ b/cypress/integration/toolbar.spec.ts @@ -9,7 +9,7 @@ describe('Toolbar Buttons', () => { const testLink = 'http://hedgedoc.org' beforeEach(() => { - cy.visit('/n/test') + cy.visitTestEditor() cy.get('.CodeMirror') .click() diff --git a/cypress/integration/yamlArrayDeprecationMessage.spec.ts b/cypress/integration/yamlArrayDeprecationMessage.spec.ts index cf29e4458..681a711ef 100644 --- a/cypress/integration/yamlArrayDeprecationMessage.spec.ts +++ b/cypress/integration/yamlArrayDeprecationMessage.spec.ts @@ -6,7 +6,7 @@ describe('YAML Array for deprecated syntax of document tags in frontmatter', () => { beforeEach(() => { - cy.visit('/n/features') + cy.visitTestEditor() }) it('is shown when using old syntax', () => { diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 073449488..f73b7cc27 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -26,3 +26,4 @@ import './config' import './fill' import './getMarkdownRenderer' import './login' +import './visit-test-editor' diff --git a/cypress/support/visit-test-editor.ts b/cypress/support/visit-test-editor.ts new file mode 100644 index 000000000..640a4e630 --- /dev/null +++ b/cypress/support/visit-test-editor.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +declare namespace Cypress { + interface Chainable { + visitTestEditor (query?: string): Chainable + } +} + +export const testNoteId = 'test' + +Cypress.Commands.add('visitTestEditor', (query?: string) => { + return cy.visit(`/n/${testNoteId}${query ? `?${query}` : ''}`) +}) + +beforeEach(() => { + cy.intercept(`/api/v2/notes/${testNoteId}-get`, { + "id": "ABC123", + "alias": "banner", + "lastChange": { + "userId": "test", + "timestamp": 1600033920 + }, + "viewCount": 0, + "createTime": 1600033920, + "content": "", + "authorship": [], + "preVersionTwoNote": true + }) +}) diff --git a/public/api/v2/notes/features-get b/public/api/v2/notes/features-get new file mode 100644 index 000000000..3ab4c076e --- /dev/null +++ b/public/api/v2/notes/features-get @@ -0,0 +1,13 @@ +{ + "id": "ABC123", + "alias": "banner", + "lastChange": { + "userId": "test", + "timestamp": 1600033920 + }, + "viewCount": 0, + "createTime": 1600033920, + "content": "---\ntitle: Features\ndescription: Many features, such wow!\nrobots: noindex\ntags:\n - hedgedoc\n - demo\n - react\nopengraph:\n title: Features\n---\n# Embedding demo\n[TOC]\n\n## markmap\n\n\n```markmap\n# MarkMap\n\n## Pro\n\n### written in typescript\n\n## Cons\n\n### must redeclare types\n```\n\n## Vega-Lite\n\n```vega-lite\n\n\n{\n \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.json\",\n \"description\": \"Reproducing http://robslink.com/SAS/democd91/pyramid_pie.htm\",\n \"data\": {\n \"values\": [\n {\"category\": \"Sky\", \"value\": 75, \"order\": 3},\n {\"category\": \"Shady side of a pyramid\", \"value\": 10, \"order\": 1},\n {\"category\": \"Sunny side of a pyramid\", \"value\": 15, \"order\": 2}\n ]\n },\n \"mark\": {\"type\": \"arc\", \"outerRadius\": 80},\n \"encoding\": {\n \"theta\": {\n \"field\": \"value\", \"type\": \"quantitative\",\n \"scale\": {\"range\": [2.35619449, 8.639379797]},\n \"stack\": true\n },\n \"color\": {\n \"field\": \"category\", \"type\": \"nominal\",\n \"scale\": {\n \"domain\": [\"Sky\", \"Shady side of a pyramid\", \"Sunny side of a pyramid\"],\n \"range\": [\"#416D9D\", \"#674028\", \"#DEAC58\"]\n },\n \"legend\": {\n \"orient\": \"none\",\n \"title\": null,\n \"columns\": 1,\n \"legendX\": 200,\n \"legendY\": 80\n }\n },\n \"order\": {\n \"field\": \"order\"\n }\n },\n \"view\": {\"stroke\": null}\n}\n\n```\n\n## GraphViz\n\n```graphviz\ngraph {\n a -- b\n a -- b\n b -- a [color=blue]\n}\n```\n\n```graphviz\ndigraph structs {\n node [shape=record];\n struct1 [label=\" left| mid\ dle| right\"];\n struct2 [label=\" one| two\"];\n struct3 [label=\"hello\nworld |{ b |{c| d|e}| f}| g | h\"];\n struct1:f1 -> struct2:f0;\n struct1:f2 -> struct3:here;\n}\n```\n\n```graphviz\ndigraph G {\n main -> parse -> execute;\n main -> init;\n main -> cleanup;\n execute -> make_string;\n execute -> printf\n init -> make_string;\n main -> printf;\n execute -> compare;\n}\n```\n\n```graphviz\ndigraph D {\n node [fontname=\"Arial\"];\n node_A [shape=record label=\"shape=record|{above|middle|below}|right\"];\n node_B [shape=plaintext label=\"shape=plaintext|{curly|braces and|bars without}|effect\"];\n}\n```\n\n```graphviz\ndigraph D {\n A -> {B, C, D} -> {F}\n}\n```\n\n## High Res Image\n\n![Wheat Field with Cypresses](/img/highres.jpg)\n\n## Sequence Diagram (deprecated)\n\n```sequence\nTitle: Here is a title\nnote over A: asdd\nA->B: Normal line\nB-->C: Dashed line\nC->>D: Open arrow\nD-->>A: Dashed open arrow\nparticipant IOOO\n```\n\n## Mermaid\n\n```mermaid\ngantt\n title A Gantt Diagram\n\n section Section\n A task: a1, 2014-01-01, 30d\n Another task: after a1, 20d\n\n section Another\n Task in sec: 2014-01-12, 12d\n Another task: 24d\n```\n\n## Flowchart\n\n```flow\nst=>start: Start\ne=>end: End\nop=>operation: My Operation\nop2=>operation: lalala\ncond=>condition: Yes or No?\n\nst->op->op2->cond\ncond(yes)->e\ncond(no)->op2\n```\n\n## ABC\n\n```abc\nX:1\nT:Speed the Plough\nM:4/4\nC:Trad.\nK:G\n|:GABc dedB|dedB dedB|c2ec B2dB|c2A2 A2BA|\nGABc dedB|dedB dedB|c2ec B2dB|A2F2 G4:|\n|:g2gf gdBd|g2f2 e2d2|c2ec B2dB|c2A2 A2df|\ng2gf g2Bd|g2f2 e2d2|c2ec B2dB|A2F2 G4:|\n```\n\n## CSV\n\n```csv delimiter=; header\nUsername; Identifier;First name;Last name\n\"booker12; rbooker\";9012;Rachel;Booker\ngrey07;2070;Laura;Grey\njohnson81;4081;Craig;Johnson\njenkins46;9346;Mary;Jenkins\nsmith79;5079;Jamie;Smith\n```\n\n## some plain text\n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n## KaTeX\nYou can render *LaTeX* mathematical expressions using **KaTeX**, as on [math.stackexchange.com](https://math.stackexchange.com/):\n\nThe *Gamma function* satisfying $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$ is via the Euler integral\n\n$$\nx = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.\n$$\n\n$$\n\\Gamma(z) = \\int_0^\\infty t^{z-1}e^{-t}dt\\,.\n$$\n\n> More information about **LaTeX** mathematical expressions [here](https://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference).\n\n## Blockquote\n> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n> [color=red] [name=John Doe] [time=2020-06-21 22:50]\n\n## Slideshare\n{%slideshare mazlan1/internet-of-things-the-tip-of-an-iceberg %}\n\n## Gist\nhttps://gist.github.com/schacon/1\n\n## YouTube\nhttps://www.youtube.com/watch?v=YE7VzlLtp-4\n\n## Vimeo\nhttps://vimeo.com/23237102\n\n## Asciinema\nhttps://asciinema.org/a/117928\n\n## PDF\n{%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %}\n\n## Code highlighting\n```js=\nvar s = \"JavaScript syntax highlighting\";\nalert(s);\nfunction $initHighlight(block, cls) {\n try {\n if (cls.search(/\\bno\\-highlight\\b/) != -1)\n return process(block, true, 0x0F) +\n ' class=\"\"';\n } catch (e) {\n /* handle exception */\n }\n for (var i = 0 / 2; i < classes.length; i++) {\n if (checkCondition(classes[i]) === undefined)\n return /\\d+[\\s/]/g;\n }\n}\n```\n\n## PlantUML\n```plantuml\n@startuml\nparticipant Alice\nparticipant \"The **Famous** Bob\" as Bob\n\nAlice -> Bob : hello --there--\n... Some ~~long delay~~ ...\nBob -> Alice : ok\nnote left\n This is **bold**\n This is //italics//\n This is \"\"monospaced\"\"\n This is --stroked--\n This is __underlined__\n This is ~~waved~~\nend note\n\nAlice -> Bob : A //well formatted// message\nnote right of Alice\n This is displayed\n __left of__ Alice.\nend note\nnote left of Bob\n This is displayed\n **left of Alice Bob**.\nend note\nnote over Alice, Bob\n This is hosted by \nend note\n@enduml\n```\n\n## ToDo List\n\n- [ ] ToDos\n - [X] Buy some salad\n - [ ] Brush teeth\n - [x] Drink some water\n - [ ] **Click my box** and see the source code, if you're allowed to edit!\n\n", + "authorship": [], + "preVersionTwoNote": true +} diff --git a/src/api/notes/index.ts b/src/api/notes/index.ts index 1762de7d1..96354009e 100644 --- a/src/api/notes/index.ts +++ b/src/api/notes/index.ts @@ -15,15 +15,17 @@ export interface Note { id: string alias: string lastChange: LastChange - viewcount: number - createtime: number + viewCount: number + createTime: number content: string authorship: number[] preVersionTwoNote: boolean } export const getNote = async (noteId: string): Promise => { - const response = await fetch(getApiUrl() + `/notes/${noteId}`, { + // The "-get" suffix is necessary, because in our mock api (filesystem) the note id might already be a folder. + // TODO: [mrdrogdrog] replace -get with actual api route as soon as api backend is ready. + const response = await fetch(getApiUrl() + `/notes/${noteId}-get`, { ...defaultFetchConfig }) expectResponseCode(response) diff --git a/src/components/common/document-title/note-title-extractor.ts b/src/components/common/document-title/note-title-extractor.ts deleted file mode 100644 index a93e620fb..000000000 --- a/src/components/common/document-title/note-title-extractor.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { YAMLMetaData } from '../../editor/yaml-metadata/yaml-metadata' - -export const extractNoteTitle = (defaultTitle: string, noteMetadata?: YAMLMetaData, firstHeading?: string): string => { - if (noteMetadata?.title && noteMetadata?.title !== '') { - return noteMetadata.title - } else if (noteMetadata?.opengraph && noteMetadata?.opengraph.get('title') && noteMetadata?.opengraph.get('title') !== '') { - return (noteMetadata?.opengraph.get('title') ?? defaultTitle) - } else { - return (firstHeading ?? defaultTitle).trim() - } -} diff --git a/src/components/editor/document-bar/revisions/revision-modal.tsx b/src/components/editor/document-bar/revisions/revision-modal.tsx index ea09dbba5..58f4fc1cf 100644 --- a/src/components/editor/document-bar/revisions/revision-modal.tsx +++ b/src/components/editor/document-bar/revisions/revision-modal.tsx @@ -1,20 +1,19 @@ /* -SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - -SPDX-License-Identifier: AGPL-3.0-only -*/ + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ import React, { useEffect, useRef, useState } from 'react' import { Alert, Button, Col, ListGroup, Modal, Row } from 'react-bootstrap' import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { useParams } from 'react-router' import { getAllRevisions, getRevision } from '../../../../api/revisions' import { Revision, RevisionListEntry } from '../../../../api/revisions/types' import { UserResponse } from '../../../../api/users/types' import { useIsDarkModeActivated } from '../../../../hooks/common/use-is-dark-mode-activated' -import { ApplicationState } from '../../../../redux' +import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content' import { CommonModal } from '../../../common/modals/common-modal' import { ShowIf } from '../../../common/show-if/show-if' import { RevisionModalListEntry } from './revision-modal-list-entry' @@ -58,7 +57,7 @@ export const RevisionModal: React.FC = ({ show, onHide }) }).catch(() => setError(true)) }, [selectedRevisionTimestamp, id]) - const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) + const markdownContent = useNoteMarkdownContent() return ( diff --git a/src/components/editor/document-bar/share/share-modal.tsx b/src/components/editor/document-bar/share/share-modal.tsx index dea2a2bd1..5a478a160 100644 --- a/src/components/editor/document-bar/share/share-modal.tsx +++ b/src/components/editor/document-bar/share/share-modal.tsx @@ -7,7 +7,7 @@ import equal from 'fast-deep-equal' import React from 'react' import { Modal } from 'react-bootstrap' -import { useTranslation , Trans } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { useParams } from 'react-router-dom' import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url' @@ -24,7 +24,7 @@ export interface ShareModalProps { export const ShareModal: React.FC = ({ show, onHide }) => { useTranslation() - const noteMetadata = useSelector((state: ApplicationState) => state.documentContent.metadata, equal) + const noteFrontmatter = useSelector((state: ApplicationState) => state.noteDetails.frontmatter, equal) const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode) const baseUrl = useFrontendBaseUrl() const { id } = useParams() @@ -39,11 +39,11 @@ export const ShareModal: React.FC = ({ show, onHide }) => { - + - + diff --git a/src/components/editor/document-renderer-pane/document-iframe.tsx b/src/components/editor/document-renderer-pane/document-iframe.tsx index c45791252..48942fe92 100644 --- a/src/components/editor/document-renderer-pane/document-iframe.tsx +++ b/src/components/editor/document-renderer-pane/document-iframe.tsx @@ -3,21 +3,24 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import equal from 'fast-deep-equal' import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useSelector } from 'react-redux' import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated' import { ApplicationState } from '../../../redux' import { isTestMode } from '../../../utils/is-test-mode' -import { ImageLightboxModal } from '../../markdown-renderer/replace-components/image/image-lightbox-modal' import { IframeEditorToRendererCommunicator } from '../../render-page/iframe-editor-to-renderer-communicator' import { ImageDetails } from '../../render-page/rendering-message' -import { ScrollingDocumentRenderPaneProps } from './scrolling-document-render-pane' +import { ScrollState } from '../scroll/scroll-props' +import { DocumentRenderPaneProps } from './document-render-pane' +import { useOnIframeLoad } from './hooks/use-on-iframe-load' +import { ShowOnPropChangeImageLightbox } from './show-on-prop-change-image-lightbox' -export const DocumentIframe: React.FC = ( +export const DocumentIframe: React.FC = ( { markdownContent, onTaskCheckedChange, - onMetadataChange, + onFrontmatterChange, scrollState, onFirstHeadingChange, wide, @@ -25,40 +28,40 @@ export const DocumentIframe: React.FC = ( onMakeScrollSource, extraClasses }) => { - const frameReference = useRef(null) const darkMode = useIsDarkModeActivated() + const [rendererReady, setRendererReady] = useState(false) const [lightboxDetails, setLightboxDetails] = useState(undefined) + const frameReference = useRef(null) const rendererOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.rendererOrigin) const renderPageUrl = `${rendererOrigin}/render` + const resetRendererReady = useCallback(() => setRendererReady(false), []) const iframeCommunicator = useMemo(() => new IframeEditorToRendererCommunicator(), []) + const onIframeLoad = useOnIframeLoad(frameReference, iframeCommunicator, rendererOrigin, renderPageUrl, resetRendererReady) + useEffect(() => () => iframeCommunicator.unregisterEventListener(), [iframeCommunicator]) - - const [rendererReady, setRendererReady] = useState(false) - useEffect(() => iframeCommunicator.onFirstHeadingChange(onFirstHeadingChange), [iframeCommunicator, onFirstHeadingChange]) - useEffect(() => iframeCommunicator.onMetaDataChange(onMetadataChange), [iframeCommunicator, onMetadataChange]) + useEffect(() => iframeCommunicator.onFrontmatterChange(onFrontmatterChange), [iframeCommunicator, onFrontmatterChange]) useEffect(() => iframeCommunicator.onSetScrollState(onScroll), [iframeCommunicator, onScroll]) useEffect(() => iframeCommunicator.onSetScrollSourceToRenderer(onMakeScrollSource), [iframeCommunicator, onMakeScrollSource]) useEffect(() => iframeCommunicator.onTaskCheckboxChange(onTaskCheckedChange), [iframeCommunicator, onTaskCheckedChange]) useEffect(() => iframeCommunicator.onImageClicked(setLightboxDetails), [iframeCommunicator]) useEffect(() => iframeCommunicator.onRendererReady(() => setRendererReady(true)), [darkMode, iframeCommunicator, scrollState, wide]) - useEffect(() => { - if (rendererReady) { - iframeCommunicator.sendSetMarkdownContent(markdownContent) - } - }, [iframeCommunicator, markdownContent, rendererReady]) useEffect(() => { if (rendererReady) { iframeCommunicator.sendSetDarkmode(darkMode) } }, [darkMode, iframeCommunicator, rendererReady]) + + const oldScrollState = useRef(undefined) useEffect(() => { - if (rendererReady) { + if (rendererReady && !equal(scrollState, oldScrollState.current)) { + oldScrollState.current = scrollState iframeCommunicator.sendScrollState(scrollState) } }, [iframeCommunicator, rendererReady, scrollState]) + useEffect(() => { if (rendererReady) { iframeCommunicator.sendSetWide(wide ?? false) @@ -69,37 +72,17 @@ export const DocumentIframe: React.FC = ( if (rendererReady) { iframeCommunicator.sendSetBaseUrl(window.location.toString()) } - }, [iframeCommunicator, rendererReady,]) + }, [iframeCommunicator, rendererReady]) - const sendToRenderPage = useRef(true) - - const onLoad = useCallback(() => { - const frame = frameReference.current - if (!frame || !frame.contentWindow) { - iframeCommunicator.unsetOtherSide() - return + useEffect(() => { + if (rendererReady) { + iframeCommunicator.sendSetMarkdownContent(markdownContent) } - - if (sendToRenderPage.current) { - iframeCommunicator.setOtherSide(frame.contentWindow, rendererOrigin) - sendToRenderPage.current = false - return - } else { - setRendererReady(false) - console.error("Navigated away from unknown URL") - frame.src = renderPageUrl - sendToRenderPage.current = true - } - }, [iframeCommunicator, renderPageUrl, rendererOrigin]) - - const hideLightbox = useCallback(() => { - setLightboxDetails(undefined) - }, []) + }, [iframeCommunicator, markdownContent, rendererReady]) return - -