migrate to next 15

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2025-04-20 21:13:59 +02:00
parent 727744b2a4
commit 5a41234c5f
No known key found for this signature in database
GPG key ID: FE1CD209E3EA5E85
30 changed files with 788 additions and 341 deletions

View file

@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" /> /// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -95,10 +95,7 @@ const rawNextConfig = {
]) ])
}, },
output: 'standalone', output: 'standalone',
swcMinify: true, outputFileTracingRoot: path.join(__dirname, '../'),
experimental: {
outputFileTracingRoot: path.join(__dirname, '../')
},
productionBrowserSourceMaps: true productionBrowserSourceMaps: true
} }
const completeNextConfig = withBundleAnalyzer(rawNextConfig) const completeNextConfig = withBundleAnalyzer(rawNextConfig)

View file

@ -92,14 +92,14 @@
"markdown-it-sub": "2.0.0", "markdown-it-sub": "2.0.0",
"markdown-it-sup": "2.0.0", "markdown-it-sup": "2.0.0",
"mermaid": "11.4.1", "mermaid": "11.4.1",
"next": "14.2.26", "next": "15.3.1",
"picocolors": "1.1.1", "picocolors": "1.1.1",
"react": "18.3.1", "react": "19.1.0",
"react-bootstrap": "2.10.9", "react-bootstrap": "2.10.9",
"react-bootstrap-icons": "1.11.5", "react-bootstrap-icons": "1.11.5",
"react-bootstrap-typeahead": "6.3.4", "react-bootstrap-typeahead": "6.3.4",
"react-diff-viewer": "3.1.1", "react-diff-viewer": "3.1.1",
"react-dom": "18.3.1", "react-dom": "19.1.0",
"react-i18next": "15.1.4", "react-i18next": "15.1.4",
"react-redux": "9.1.2", "react-redux": "9.1.2",
"react-use": "17.5.1", "react-use": "17.5.1",
@ -119,13 +119,13 @@
"yjs": "13.6.23" "yjs": "13.6.23"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "14.2.23", "@next/bundle-analyzer": "15.3.1",
"@testing-library/cypress": "10.0.3", "@testing-library/cypress": "10.0.3",
"@testing-library/dom": "10.4.0", "@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3", "@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1", "@testing-library/react": "16.3.0",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.5.2", "@testing-library/user-event": "14.6.1",
"@types/d3-graphviz": "2.6.10", "@types/d3-graphviz": "2.6.10",
"@types/diff": "6.0.0", "@types/diff": "6.0.0",
"@types/dompurify": "3.0.5", "@types/dompurify": "3.0.5",
@ -136,29 +136,29 @@
"@types/markdown-it-container": "2.0.10", "@types/markdown-it-container": "2.0.10",
"@types/markdown-it-plantuml": "1.4.5", "@types/markdown-it-plantuml": "1.4.5",
"@types/node": "20.16.15", "@types/node": "20.16.15",
"@types/react": "18.3.20", "@types/react": "19.1.2",
"@types/react-dom": "18.3.6", "@types/react-dom": "19.1.2",
"@types/ws": "8.5.14", "@types/ws": "8.5.14",
"@typescript-eslint/eslint-plugin": "8.14.0", "@typescript-eslint/eslint-plugin": "8.30.1",
"@typescript-eslint/parser": "8.14.0", "@typescript-eslint/parser": "8.30.1",
"csstype": "3.1.3", "csstype": "3.1.3",
"cypress": "13.17.0", "cypress": "13.17.0",
"cypress-commands": "3.0.0", "cypress-commands": "3.0.0",
"cypress-fill-command": "1.0.2", "cypress-fill-command": "1.0.2",
"dotenv-cli": "7.4.4", "dotenv-cli": "7.4.4",
"eslint": "8.57.1", "eslint": "9.25.0",
"eslint-config-next": "14.2.23", "eslint-config-next": "15.3.1",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-chai-friendly": "1.0.1", "eslint-plugin-chai-friendly": "1.0.1",
"eslint-plugin-cypress": "4.1.0", "eslint-plugin-cypress": "4.2.1",
"eslint-plugin-jest": "28.9.0", "eslint-plugin-jest": "28.11.0",
"eslint-plugin-n": "17.13.2", "eslint-plugin-n": "17.17.0",
"eslint-plugin-prettier": "5.2.3", "eslint-plugin-prettier": "5.2.6",
"eslint-plugin-promise": "7.1.0", "eslint-plugin-promise": "7.2.1",
"eslint-plugin-testing-library": "6.4.0", "eslint-plugin-testing-library": "7.1.1",
"jest": "29.7.0", "jest": "29.7.0",
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "29.7.0",
"prettier": "3.3.3", "prettier": "3.5.3",
"react-test-renderer": "18.3.1", "react-test-renderer": "18.3.1",
"ts-loader": "9.5.2", "ts-loader": "9.5.2",
"ts-mockery": "1.2.0", "ts-mockery": "1.2.0",
@ -166,5 +166,9 @@
"typescript": "5.6.3", "typescript": "5.6.3",
"user-agent-data-types": "0.4.2" "user-agent-data-types": "0.4.2"
}, },
"packageManager": "yarn@4.5.3" "packageManager": "yarn@4.5.3",
"resolutions": {
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2"
}
} }

View file

@ -9,13 +9,14 @@ import { baseUrlFromEnvExtractor } from '../../../utils/base-url-from-env-extrac
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
interface PageProps { interface PageProps {
params: { id: string | undefined } params: Promise<{ id: string | undefined }>
} }
/** /**
* Redirects the user to the editor if the link is a root level direct link to a version 1 note. * Redirects the user to the editor if the link is a root level direct link to a version 1 note.
*/ */
const DirectLinkFallback = async ({ params }: PageProps) => { const DirectLinkFallback = async (props: PageProps) => {
const params = await props.params
const baseUrl = baseUrlFromEnvExtractor.extractBaseUrls().editor const baseUrl = baseUrlFromEnvExtractor.extractBaseUrls().editor
if (params.id === undefined) { if (params.id === undefined) {

View file

@ -9,16 +9,17 @@ import { NoteLoadingBoundary } from '../../../../components/common/note-loading-
import { EditorPageContent } from '../../../../components/editor-page/editor-page-content' import { EditorPageContent } from '../../../../components/editor-page/editor-page-content'
import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import React from 'react' import React, { use } from 'react'
interface PageParams { interface PageParams {
params: NoteIdProps params: Promise<NoteIdProps>
} }
/** /**
* Renders a page that is used by the user to edit markdown notes. It contains the editor and a renderer. * Renders a page that is used by the user to edit markdown notes. It contains the editor and a renderer.
*/ */
const EditorPage: NextPage<PageParams> = ({ params }) => { const EditorPage: NextPage<PageParams> = (props: PageParams) => {
const params = use(props.params)
return ( return (
<NoteLoadingBoundary noteId={params.noteId}> <NoteLoadingBoundary noteId={params.noteId}>
<EditorToRendererCommunicatorContextProvider> <EditorToRendererCommunicatorContextProvider>

View file

@ -11,16 +11,17 @@ import { useNoteAndAppTitle } from '../../../../components/editor-page/head-meta
import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider'
import { SlideShowPageContent } from '../../../../components/slide-show-page/slide-show-page-content' import { SlideShowPageContent } from '../../../../components/slide-show-page/slide-show-page-content'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import React from 'react' import React, { use } from 'react'
interface PageParams { interface PageParams {
params: NoteIdProps params: Promise<NoteIdProps>
} }
/** /**
* Renders a page that is used by the user to hold a presentation. It contains the renderer for the presentation. * Renders a page that is used by the user to hold a presentation. It contains the renderer for the presentation.
*/ */
const SlideShowPage: NextPage<PageParams> = ({ params }) => { const SlideShowPage: NextPage<PageParams> = (props) => {
const params = use(props.params)
useNoteAndAppTitle() useNoteAndAppTitle()
return ( return (

View file

@ -10,16 +10,17 @@ import { DocumentReadOnlyPageContent } from '../../../../components/document-rea
import { useNoteAndAppTitle } from '../../../../components/editor-page/head-meta-properties/use-note-and-app-title' import { useNoteAndAppTitle } from '../../../../components/editor-page/head-meta-properties/use-note-and-app-title'
import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import { EditorToRendererCommunicatorContextProvider } from '../../../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import React from 'react' import React, { use } from 'react'
interface PageParams { interface PageParams {
params: NoteIdProps params: Promise<NoteIdProps>
} }
/** /**
* Renders a page that contains only the rendered document without an editor or realtime updates. * Renders a page that contains only the rendered document without an editor or realtime updates.
*/ */
const DocumentReadOnlyPage: NextPage<PageParams> = ({ params }) => { const DocumentReadOnlyPage: NextPage<PageParams> = (props) => {
const params = use(props.params)
useNoteAndAppTitle() useNoteAndAppTitle()
return ( return (

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import styles from './animations.module.scss' import styles from './animations.module.scss'
import React, { Fragment, useEffect, useState } from 'react' import React, { Fragment, useEffect, useState, type JSX } from 'react'
import type { Icon } from 'react-bootstrap-icons' import type { Icon } from 'react-bootstrap-icons'
import { import {
File as IconFile, File as IconFile,

View file

@ -27,7 +27,7 @@ enum SHOW_STATE {
* @return the copy function and the overlay * @return the copy function and the overlay
*/ */
export const useCopyOverlay = ( export const useCopyOverlay = (
clickComponent: RefObject<HTMLElement>, clickComponent: RefObject<HTMLElement | null>,
content: string content: string
): [copyToCliphoard: () => void, overlayElement: ReactElement] => { ): [copyToCliphoard: () => void, overlayElement: ReactElement] => {
useTranslation() useTranslation()

View file

@ -19,7 +19,7 @@ const log = new Logger('IframeLoader')
* @param onNavigateAway An optional callback that is executed when the iframe leaves the enforced URL. * @param onNavigateAway An optional callback that is executed when the iframe leaves the enforced URL.
*/ */
export const useForceRenderPageUrlOnIframeLoadCallback = ( export const useForceRenderPageUrlOnIframeLoadCallback = (
iFrameReference: RefObject<HTMLIFrameElement>, iFrameReference: RefObject<HTMLIFrameElement | null>,
onNavigateAway: () => void onNavigateAway: () => void
): (() => void) => { ): (() => void) => {
const iframeCommunicator = useEditorToRendererCommunicator() const iframeCommunicator = useEditorToRendererCommunicator()

View file

@ -36,7 +36,7 @@ const applyScrollState = (view: EditorView, scrollState: ScrollState): void => {
* @param scrollState The scroll state that should be monitored * @param scrollState The scroll state that should be monitored
*/ */
export const useApplyScrollState = (scrollState: ScrollState | null): void => { export const useApplyScrollState = (scrollState: ScrollState | null): void => {
const lastScrollPosition = useRef<ScrollState>() const lastScrollPosition = useRef<ScrollState>(undefined)
const [codeMirrorRef] = useCodemirrorReferenceContext() const [codeMirrorRef] = useCodemirrorReferenceContext()
useEffect(() => { useEffect(() => {

View file

@ -15,7 +15,7 @@ import { useMemo, useRef } from 'react'
* @return the generated callback * @return the generated callback
*/ */
export const useCursorActivityCallback = (): Extension => { export const useCursorActivityCallback = (): Extension => {
const lastMainSelection = useRef<SelectionRange>() const lastMainSelection = useRef<SelectionRange>(undefined)
return useMemo( return useMemo(
() => () =>

View file

@ -13,7 +13,7 @@ import { useEffect, useRef } from 'react'
* @param messageTransporter the message transporter to disconnect * @param messageTransporter the message transporter to disconnect
*/ */
export const useDisconnectOnUserLoginStatusChange = (messageTransporter: MessageTransporter): void => { export const useDisconnectOnUserLoginStatusChange = (messageTransporter: MessageTransporter): void => {
const previousIsLoggedIn = useRef<boolean | undefined>() const previousIsLoggedIn = useRef<boolean | undefined>(undefined)
const isLoggedIn = useApplicationState((state) => state.user !== null) const isLoggedIn = useApplicationState((state) => state.user !== null)
useEffect(() => { useEffect(() => {
if (previousIsLoggedIn.current === undefined) { if (previousIsLoggedIn.current === undefined) {

View file

@ -40,7 +40,7 @@ export const EmojiPickerPopover = React.forwardRef<HTMLDivElement, EmojiPickerPr
({ onEmojiSelected, ...props }, ref) => { ({ onEmojiSelected, ...props }, ref) => {
const darkModeEnabled = useDarkModeState() const darkModeEnabled = useDarkModeState()
const pickerContainerRef = useRef<HTMLDivElement>(null) const pickerContainerRef = useRef<HTMLDivElement>(null)
const pickerRef = useRef<Picker>() const pickerRef = useRef<Picker>(undefined)
useEffect(() => { useEffect(() => {
if (!pickerContainerRef.current) { if (!pickerContainerRef.current) {

View file

@ -16,7 +16,7 @@ export interface ToolbarButtonProps {
icon: Icon icon: Icon
onClick: () => void onClick: () => void
disabled?: boolean disabled?: boolean
buttonRef?: RefObject<HTMLButtonElement> buttonRef?: RefObject<HTMLButtonElement | null>
} }
/** /**

View file

@ -21,7 +21,7 @@ const logger = new Logger('Upload image button')
* Shows a button that uploads a chosen file to the backend and adds the link to the note. * Shows a button that uploads a chosen file to the backend and adds the link to the note.
*/ */
export const UploadImageButton: React.FC = () => { export const UploadImageButton: React.FC = () => {
const clickRef = useRef<() => void>() const clickRef = useRef<() => void>(undefined)
const buttonClick = useCallback(() => { const buttonClick = useCallback(() => {
clickRef.current?.() clickRef.current?.()
}, []) }, [])

View file

@ -20,7 +20,7 @@ const log = new Logger('useScrollState')
* @return the created scroll state and the update function. The update function accepts only new values if the given scroll source isn't active to prevent callback loops. * @return the created scroll state and the update function. The update function accepts only new values if the given scroll source isn't active to prevent callback loops.
*/ */
export const useScrollState = ( export const useScrollState = (
scrollSourceRef: RefObject<ScrollSource>, scrollSourceRef: RefObject<ScrollSource | null>,
scrollSource: ScrollSource scrollSource: ScrollSource
): [scrollState: ScrollState, onScroll: (newScrollState: ScrollState) => void] => { ): [scrollState: ScrollState, onScroll: (newScrollState: ScrollState) => void] => {
const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll)

View file

@ -39,7 +39,7 @@ export const ImportMarkdownSidebarEntry: React.FC = () => {
[changeEditorContent] [changeEditorContent]
) )
const clickRef = useRef<() => void>() const clickRef = useRef<() => void>(undefined)
const buttonClick = useCallback(() => { const buttonClick = useCallback(() => {
clickRef.current?.() clickRef.current?.()
}, []) }, [])

View file

@ -15,7 +15,7 @@ export interface SpecificSidebarEntryProps {
export interface SidebarEntryProps extends PropsWithDataCypressId { export interface SidebarEntryProps extends PropsWithDataCypressId {
icon?: Icon icon?: Icon
buttonRef?: RefObject<HTMLButtonElement> buttonRef?: RefObject<HTMLButtonElement | null>
hide?: boolean hide?: boolean
className?: string className?: string
onClick?: () => void onClick?: () => void

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { headers } from 'next/headers' import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
import type { PropsWithChildren } from 'react' import type { PropsWithChildren } from 'react'
import React from 'react' import React from 'react'
@ -17,7 +17,7 @@ export interface ExpectedOriginBoundaryProps extends PropsWithChildren {
* @return the calculated request origin or {@code undefined} if no host header has been found * @return the calculated request origin or {@code undefined} if no host header has been found
*/ */
export const buildOriginFromHeaders = (): string | undefined => { export const buildOriginFromHeaders = (): string | undefined => {
const currentHeader = headers() const currentHeader = headers() as unknown as UnsafeUnwrappedHeaders
const host = currentHeader.get('x-forwarded-host') ?? currentHeader.get('host') const host = currentHeader.get('x-forwarded-host') ?? currentHeader.get('host')
if (host === null) { if (host === null) {
return undefined return undefined

View file

@ -22,7 +22,6 @@ export class AnchorNodePreprocessor extends TravelerNodeProcessor {
const url = node.attribs.href.trim() const url = node.attribs.href.trim()
// eslint-disable-next-line no-script-url
if (url.startsWith('data:') || url.startsWith('javascript:') || url.startsWith('vbscript:')) { if (url.startsWith('data:') || url.startsWith('javascript:') || url.startsWith('vbscript:')) {
delete node.attribs.href delete node.attribs.href
return return

View file

@ -54,11 +54,11 @@ const calculateLineMarkerPositions = (
* @param onLineMarkerPositionChanged The callback to call if the {@link LineMarkerPosition line marker positions} change e.g. by rendering or resizing. * @param onLineMarkerPositionChanged The callback to call if the {@link LineMarkerPosition line marker positions} change e.g. by rendering or resizing.
*/ */
export const useCalculateLineMarkerPosition = ( export const useCalculateLineMarkerPosition = (
documentElement: RefObject<HTMLDivElement>, documentElement: RefObject<HTMLDivElement | null>,
lineMarkers: LineMarkers[] | undefined, lineMarkers: LineMarkers[] | undefined,
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
): void => { ): void => {
const lastLineMarkerPositions = useRef<LineMarkerPosition[]>() const lastLineMarkerPositions = useRef<LineMarkerPosition[]>(undefined)
const calculateNewLineMarkerPositions = useCallback(() => { const calculateNewLineMarkerPositions = useCallback(() => {
if (!documentElement.current || !onLineMarkerPositionChanged || !lineMarkers) { if (!documentElement.current || !onLineMarkerPositionChanged || !lineMarkers) {

View file

@ -14,7 +14,7 @@ import { useEffect, useRef } from 'react'
* @param onChange The callback to call if something changes * @param onChange The callback to call if something changes
*/ */
export const useOnRefChange = <T>(reference: MutableRefObject<T>, onChange?: (newValue: T) => void): void => { export const useOnRefChange = <T>(reference: MutableRefObject<T>, onChange?: (newValue: T) => void): void => {
const lastValue = useRef<T>() const lastValue = useRef<T>(undefined)
useEffect(() => { useEffect(() => {
if (onChange && !equal(reference.current, lastValue.current)) { if (onChange && !equal(reference.current, lastValue.current)) {
lastValue.current = reference.current lastValue.current = reference.current

View file

@ -23,8 +23,8 @@ import { useCallback, useMemo, useState } from 'react'
* The second one should be executed if the user actually scrolls. Usually it should be attached to the DOM element that the user scrolls. * The second one should be executed if the user actually scrolls. Usually it should be attached to the DOM element that the user scrolls.
*/ */
export const useDocumentSyncScrolling = ( export const useDocumentSyncScrolling = (
outerContainerRef: React.RefObject<HTMLElement>, outerContainerRef: React.RefObject<HTMLElement | null>,
rendererRef: React.RefObject<HTMLElement>, rendererRef: React.RefObject<HTMLElement | null>,
numberOfLines: number, numberOfLines: number,
scrollState: ScrollState | null, scrollState: ScrollState | null,
onScroll: null | ((scrollState: ScrollState) => void) onScroll: null | ((scrollState: ScrollState) => void)

View file

@ -18,7 +18,7 @@ import { useCallback } from 'react'
*/ */
export const useOnUserScroll = ( export const useOnUserScroll = (
lineMarks: LineMarkerPosition[] | undefined, lineMarks: LineMarkerPosition[] | undefined,
scrollContainer: React.RefObject<HTMLElement>, scrollContainer: React.RefObject<HTMLElement | null>,
onScroll: ((newScrollState: ScrollState) => void) | null onScroll: ((newScrollState: ScrollState) => void) | null
): React.UIEventHandler<HTMLElement> => { ): React.UIEventHandler<HTMLElement> => {
return useCallback(() => { return useCallback(() => {

View file

@ -21,9 +21,9 @@ export const useScrollToLineMark = (
scrollState: ScrollState | null, scrollState: ScrollState | null,
lineMarks: LineMarkerPosition[], lineMarks: LineMarkerPosition[],
contentLineCount: number, contentLineCount: number,
scrollContainer: RefObject<HTMLElement> scrollContainer: RefObject<HTMLElement | null>
): void => { ): void => {
const lastScrollPosition = useRef<number>() const lastScrollPosition = useRef<number>(undefined)
const scrollTo = useCallback( const scrollTo = useCallback(
(targetPosition: number): void => { (targetPosition: number): void => {

View file

@ -14,7 +14,7 @@ import { useEffect, useRef, useState } from 'react'
* @param onHeightChange The callback that should be executed if the height changes * @param onHeightChange The callback that should be executed if the height changes
*/ */
export const useOnHeightChange = ( export const useOnHeightChange = (
elementRef: RefObject<HTMLElement>, elementRef: RefObject<HTMLElement | null>,
onHeightChange: undefined | ((value: number) => void) onHeightChange: undefined | ((value: number) => void)
): void => { ): void => {
const [rendererSize, setRendererSize] = useState<number>(0) const [rendererSize, setRendererSize] = useState<number>(0)

View file

@ -62,7 +62,7 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
) )
const markdownBodyRef = useRef<HTMLDivElement>(null) const markdownBodyRef = useRef<HTMLDivElement>(null)
const currentLineMarkers = useRef<LineMarkers[]>() const currentLineMarkers = useRef<LineMarkers[]>(undefined)
const extensions = useMarkdownExtensions( const extensions = useMarkdownExtensions(
baseUrl, baseUrl,

View file

@ -10,7 +10,7 @@ import { Vimeo as IconVimeo } from 'react-bootstrap-icons'
interface VimeoApiResponse { interface VimeoApiResponse {
// Vimeo uses strange names for their fields. ESLint doesn't like that. // Vimeo uses strange names for their fields. ESLint doesn't like that.
// eslint-disable-next-line camelcase
thumbnail_large?: string thumbnail_large?: string
} }

1001
yarn.lock

File diff suppressed because it is too large Load diff