From 28512501755c013f458b668fba8a32e9110ce0eb Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 3 Oct 2024 04:23:56 +0200 Subject: [PATCH 1/6] upd kit-codemirror; basic demo app test; simpler demo app PageHome; rm unused progress control context in demo; --- apps/demo/src/App.tsx | 9 +-- .../components/CustomWidgets/WidgetCode.tsx | 2 +- apps/demo/src/components/Layout.tsx | 6 -- apps/demo/src/pages/PageComplex.tsx | 5 ++ apps/demo/src/pages/PageHome.tsx | 11 ---- apps/demo/tests/PageHome.test.tsx | 65 +++++++++++++++---- apps/sandbox/package.json | 4 +- babelImportDefaultPlugin.js | 12 ++-- jest.config.ts | 18 ++--- package-lock.json | 49 +++++++------- package.json | 4 +- packages/input/package.json | 4 +- 12 files changed, 104 insertions(+), 85 deletions(-) diff --git a/apps/demo/src/App.tsx b/apps/demo/src/App.tsx index e9c215d..3c038f9 100644 --- a/apps/demo/src/App.tsx +++ b/apps/demo/src/App.tsx @@ -19,7 +19,6 @@ import { useViewSettings } from './lib/ViewSettings' import I18NextChainedBackend from 'i18next-chained-backend' import I18NextLocalStorageBackend from 'i18next-localstorage-backend' import I18nextBrowserLanguageDetector from 'i18next-browser-languagedetector' -import { ProgressControlProvider } from 'react-progress-state' import { UIApiProvider } from '@ui-schema/ui-schema/UIApi' import { LocalizationProvider } from '@mui/x-date-pickers' import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment' @@ -116,11 +115,9 @@ export const App: React.ComponentType<{}> = () => { - - - - - + + + diff --git a/apps/demo/src/components/CustomWidgets/WidgetCode.tsx b/apps/demo/src/components/CustomWidgets/WidgetCode.tsx index 02a781d..13a17ad 100644 --- a/apps/demo/src/components/CustomWidgets/WidgetCode.tsx +++ b/apps/demo/src/components/CustomWidgets/WidgetCode.tsx @@ -8,7 +8,7 @@ import { javascript } from '@codemirror/lang-javascript' import { html } from '@codemirror/lang-html' import { css } from '@codemirror/lang-css' import { extractValue } from '@ui-schema/ui-schema/UIStore' -import { WidgetCode } from '@ui-schema/material-code' +import { WidgetCode } from '@ui-schema/material-code/WidgetCode' import { WidgetCodeSelectable } from '@ui-schema/material-code/WidgetCodeSelectable' import { CustomCodeMirror } from '../CustomCodeMirror' diff --git a/apps/demo/src/components/Layout.tsx b/apps/demo/src/components/Layout.tsx index 85548d3..54ad633 100644 --- a/apps/demo/src/components/Layout.tsx +++ b/apps/demo/src/components/Layout.tsx @@ -17,7 +17,6 @@ import ListItemButton from '@mui/material/ListItemButton' import ListItemText from '@mui/material/ListItemText' import { useTranslation } from 'react-i18next' import Box from '@mui/material/Box' -import { useProgressControlReset } from 'react-progress-state' import Tooltip from '@mui/material/Tooltip' import { PageComplex } from '../pages/PageComplex' import { PageInput } from '../pages/PageInput' @@ -105,11 +104,6 @@ const basePath = config.BASE_PATH export const Layout: React.ComponentType<{}> = () => { const {snackbars, rmNotice} = useSnack() const scrollWrapper = React.useRef(null) - const {resetScopes} = useProgressControlReset() - - React.useLayoutEffect(() => { - return () => resetScopes([]) - }, [resetScopes]) return <>
diff --git a/apps/demo/src/pages/PageComplex.tsx b/apps/demo/src/pages/PageComplex.tsx index c8643af..289b5d9 100644 --- a/apps/demo/src/pages/PageComplex.tsx +++ b/apps/demo/src/pages/PageComplex.tsx @@ -11,6 +11,7 @@ import { config } from '../config' import LinearProgress from '@mui/material/LinearProgress' import Alert from '@mui/material/Alert' import AlertTitle from '@mui/material/AlertTitle' +import { ApiPing } from '../components/ApiPing' import IcRefresh from '@mui/icons-material/Refresh' import { IconButtonProgress } from '@ui-controls/progress/IconButtonProgress' import FormControl from '@mui/material/FormControl' @@ -128,6 +129,10 @@ export const PageComplex: React.ComponentType = () => { onMount /> : null} + + + + diff --git a/apps/demo/src/pages/PageHome.tsx b/apps/demo/src/pages/PageHome.tsx index a007cb4..a7a4d84 100644 --- a/apps/demo/src/pages/PageHome.tsx +++ b/apps/demo/src/pages/PageHome.tsx @@ -4,7 +4,6 @@ import React from 'react' import Helmet from 'react-helmet' import { useTranslation } from 'react-i18next' import Container from '@mui/material/Container' -import { ApiPing } from '../components/ApiPing' import Grid2 from '@mui/material/Unstable_Grid2' import { ViewerFromText } from '@content-ui/md-mui/Viewer' @@ -14,13 +13,6 @@ Lorem ipsum dolor sit amet... Hey there this is some content, rendered from Markdown as ReactJS components using MUI. -\`\`\`json -{ - "demo": true, - "val": "test" -} -\`\`\` - Some code: \`var some = true\`. > Blockquotes :+1: @@ -52,9 +44,6 @@ export const PageHome: React.ComponentType = () => { /> - - - diff --git a/apps/demo/tests/PageHome.test.tsx b/apps/demo/tests/PageHome.test.tsx index 4b641cb..1377d30 100644 --- a/apps/demo/tests/PageHome.test.tsx +++ b/apps/demo/tests/PageHome.test.tsx @@ -4,26 +4,67 @@ import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeaf' import { it, expect, describe } from '@jest/globals' import '@testing-library/jest-dom/jest-globals' -import { render } from '@testing-library/react' +import { render, screen } from '@testing-library/react' +import i18n from 'i18next' +import I18NextChainedBackend from 'i18next-chained-backend' +import resourcesToBackend from 'i18next-resources-to-backend' +import { Suspense } from 'react' +import { initReactI18next } from 'react-i18next' +import { BrowserRouter } from 'react-router-dom' import { contentUIMapping } from '../src/components/ContentUI' import { PageHome } from '../src/pages/PageHome' // rm -rf node_modules/.cache && npm run test -- --testPathPattern=PageHome --no-cache +// todo: demo-app tests have some issues with `CodeMirror` being imported in different ways +// Error: Uncaught [Error: Unrecognized extension value in extension set ([object Object]). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.] +// - maybe as `kit-codemirror`/`material-code` and `md-mui` is not yet strict ESM, but this project is +// - webpack has no issue with it, but webpack follows the `main/module` package.json settings, while ts-jest only follows `type: "module"` for ESM resolving + describe('PageHome', () => { - // it('DummyText', async() => { - // const {queryByText} = render( - // {'PageHome'}, - // ) - // expect(queryByText('PageHome')).toBeInTheDocument() - // }) + i18n + .use(I18NextChainedBackend) + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + supportedLngs: ['en'], + fallbackLng: 'en', + backend: { + backendOptions: [ + { + versions: { + en: '0.0.1', + }, + }, + ], + backends: [ + resourcesToBackend((language, namespace, callback) => { + import(`../src/locales/${language}/${namespace}.json`) + .then((resources) => { + callback(null, resources) + }) + .catch((error) => { + console.error('Error loading translation for:', language, namespace, error) + callback(error, null) + }) + }), + ], + }, + interpolation: { + escapeValue: false, + }, + detection: {}, + }) it('PageHome Content', async() => { - const {queryByText} = render( - - - , + render( + + + + + + + , ) - expect(queryByText('Content-UI Demo')).toBeInTheDocument() + expect(await screen.findByText('Content-UI Demo')).toBeInTheDocument() }) }) diff --git a/apps/sandbox/package.json b/apps/sandbox/package.json index 5933615..bfc819b 100644 --- a/apps/sandbox/package.json +++ b/apps/sandbox/package.json @@ -40,8 +40,8 @@ "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", "@ui-schema/ds-material": "~0.4.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", - "@ui-schema/material-code": "~0.4.2", + "@ui-schema/kit-codemirror": "~0.1.1", + "@ui-schema/material-code": "~0.4.4", "@ui-schema/ui-schema": "~0.4.5", "cross-env": "^7.0.3", "react": "^18.3.1", diff --git a/babelImportDefaultPlugin.js b/babelImportDefaultPlugin.js index 6c52d79..c628e1e 100644 --- a/babelImportDefaultPlugin.js +++ b/babelImportDefaultPlugin.js @@ -15,7 +15,6 @@ export default function({types: t}) { ImportDeclaration(path, state) { const source = path.node.source.value - // Log the original source and specifiers for debugging // console.log(`Processing import from: ${source}`) // console.log(`Specifiers: ${JSON.stringify(path.node.specifiers, null, 2)}`) @@ -28,12 +27,11 @@ export default function({types: t}) { // Check if the import already has "Module" suffix to prevent infinite loops if(originalImportName.endsWith('Module')) { - console.log(`Skipping transformation for ${originalImportName} to avoid infinite loop.`) + // console.log(`Skipping transformation for ${originalImportName} to avoid infinite loop.`) return // Stop further transformation } if(isLikelyCommonJS(source, state)) { - // Log the transformation we are about to apply console.log(`Transforming ${originalImportName} from CommonJS`) path.replaceWithMultiple([ @@ -49,8 +47,7 @@ export default function({types: t}) { ]), ]) - // Log the final transformation result - console.log(`Created new import: ${originalImportName}Module`) + // console.log(`Created new import: ${originalImportName}Module`) } else { // console.log(`${source} is likely ESM, no transformation applied.`) } @@ -71,8 +68,9 @@ export default function({types: t}) { return ( !source.endsWith('.mjs') && ( - /node_modules/.test(source) || - source.startsWith('@mui') + /node_modules/.test(source) + || source.startsWith('@mui') + || source.startsWith('react-helmet') ) ) } diff --git a/jest.config.ts b/jest.config.ts index 7443cae..014c9ce 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -85,15 +85,15 @@ const config: Config.InitialOptions = { coverageDirectory: '/coverage', projects: [ // todo: enable app tests again when fixed ESM/CJS issues - // { - // displayName: 'test-apps-demo', - // ...base, - // moduleDirectories: ['node_modules', '/apps/demo/node_modules'], - // testMatch: [ - // '/apps/demo/src/**/*.(test|spec).(js|ts|tsx)', - // '/apps/demo/tests/**/*.(test|spec).(js|ts|tsx)', - // ], - // }, + { + displayName: 'test-apps-demo', + ...base, + moduleDirectories: ['node_modules', '/apps/demo/node_modules'], + testMatch: [ + '/apps/demo/src/**/*.(test|spec).(js|ts|tsx)', + '/apps/demo/tests/**/*.(test|spec).(js|ts|tsx)', + ], + }, ...packages.map(pkg => ({ displayName: 'test-' + pkg[0], ...base, diff --git a/package-lock.json b/package-lock.json index a11c26e..07687ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,8 +35,8 @@ "@ui-controls/progress": "~0.0.4", "@ui-schema/dictionary": "~0.0.11", "@ui-schema/ds-material": "~0.4.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", - "@ui-schema/material-code": "~0.4.2", + "@ui-schema/kit-codemirror": "~0.1.1", + "@ui-schema/material-code": "~0.4.4", "@ui-schema/material-pickers": "^0.4.0-alpha.4", "@ui-schema/ui-schema": "~0.4.5", "i18next": "^22.0.4", @@ -147,8 +147,8 @@ "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", "@ui-schema/ds-material": "~0.4.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", - "@ui-schema/material-code": "~0.4.2", + "@ui-schema/kit-codemirror": "~0.1.1", + "@ui-schema/material-code": "~0.4.4", "@ui-schema/ui-schema": "~0.4.5", "cross-env": "^7.0.3", "react": "^18.3.1", @@ -5810,9 +5810,9 @@ } }, "node_modules/@ui-schema/kit-codemirror": { - "version": "0.1.0-alpha.1", - "resolved": "https://registry.npmjs.org/@ui-schema/kit-codemirror/-/kit-codemirror-0.1.0-alpha.1.tgz", - "integrity": "sha512-CKzbHKdutfq6LbZxe6awJm7DwCLaCHdL1KjGKDYU1GZZigTVlwHCATUxDT1F6TqWt+7Y2IYITfozFrIxIrnbgQ==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@ui-schema/kit-codemirror/-/kit-codemirror-0.1.1.tgz", + "integrity": "sha512-Xg/X/+TudlBZIn2L/kfkTLVoCRXpNZpCPqpHddsjQTXfdeMbT/8iieCYgWtSUoWRegx63yWxe++s0S+2rFCXkw==", "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -5821,12 +5821,9 @@ } }, "node_modules/@ui-schema/material-code": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@ui-schema/material-code/-/material-code-0.4.2.tgz", - "integrity": "sha512-D9FNQ1kmebG/SXN7DLNy0wHKIIhUe+kKPpJmaTDSABXf/0Cs2yLZ3cv8Q7vPvix9q50CMKbppSvCDLtT5Kw/NQ==", - "dependencies": { - "react-uid": "^2.2.0" - }, + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@ui-schema/material-code/-/material-code-0.4.4.tgz", + "integrity": "sha512-yJ54Sxt74y81KtF3weXwzuDD6X7YGI8L58zxvOiR9PNJFFdG8ukrka+r7Oif1WonzseYS7ccrYGbnuovwsCTzg==", "peerDependencies": { "@codemirror/language": "^6.1.0", "@codemirror/state": "^6.0.0", @@ -20788,7 +20785,7 @@ "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", + "@ui-schema/kit-codemirror": "~0.1.1", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0", "react-progress-state": "~0.3.1" @@ -20800,7 +20797,7 @@ "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", + "@ui-schema/kit-codemirror": "0.1.0-alpha.1 || ~0.1.1", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0", "react-progress-state": "~0.2.6 || ~0.3.1" @@ -22590,7 +22587,7 @@ "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", + "@ui-schema/kit-codemirror": "~0.1.1", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0", "react-progress-state": "~0.3.1" @@ -25025,18 +25022,16 @@ } }, "@ui-schema/kit-codemirror": { - "version": "0.1.0-alpha.1", - "resolved": "https://registry.npmjs.org/@ui-schema/kit-codemirror/-/kit-codemirror-0.1.0-alpha.1.tgz", - "integrity": "sha512-CKzbHKdutfq6LbZxe6awJm7DwCLaCHdL1KjGKDYU1GZZigTVlwHCATUxDT1F6TqWt+7Y2IYITfozFrIxIrnbgQ==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@ui-schema/kit-codemirror/-/kit-codemirror-0.1.1.tgz", + "integrity": "sha512-Xg/X/+TudlBZIn2L/kfkTLVoCRXpNZpCPqpHddsjQTXfdeMbT/8iieCYgWtSUoWRegx63yWxe++s0S+2rFCXkw==", "requires": {} }, "@ui-schema/material-code": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@ui-schema/material-code/-/material-code-0.4.2.tgz", - "integrity": "sha512-D9FNQ1kmebG/SXN7DLNy0wHKIIhUe+kKPpJmaTDSABXf/0Cs2yLZ3cv8Q7vPvix9q50CMKbppSvCDLtT5Kw/NQ==", - "requires": { - "react-uid": "^2.2.0" - } + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@ui-schema/material-code/-/material-code-0.4.4.tgz", + "integrity": "sha512-yJ54Sxt74y81KtF3weXwzuDD6X7YGI8L58zxvOiR9PNJFFdG8ukrka+r7Oif1WonzseYS7ccrYGbnuovwsCTzg==", + "requires": {} }, "@ui-schema/material-pickers": { "version": "0.4.0-alpha.4", @@ -26332,8 +26327,8 @@ "@types/react-dom": "^18.3.0", "@ui-controls/progress": "~0.0.4", "@ui-schema/ds-material": "~0.4.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", - "@ui-schema/material-code": "~0.4.2", + "@ui-schema/kit-codemirror": "~0.1.1", + "@ui-schema/material-code": "~0.4.4", "@ui-schema/ui-schema": "~0.4.5", "@vitejs/plugin-react": "^4.0.0", "cross-env": "^7.0.3", diff --git a/package.json b/package.json index a2e8cdc..9197c51 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,8 @@ "@ui-controls/progress": "~0.0.4", "@ui-schema/dictionary": "~0.0.11", "@ui-schema/ds-material": "~0.4.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", - "@ui-schema/material-code": "~0.4.2", + "@ui-schema/kit-codemirror": "~0.1.1", + "@ui-schema/material-code": "~0.4.4", "@ui-schema/material-pickers": "^0.4.0-alpha.4", "@ui-schema/ui-schema": "~0.4.5", "i18next": "^22.0.4", diff --git a/packages/input/package.json b/packages/input/package.json index aac21a5..60a6707 100644 --- a/packages/input/package.json +++ b/packages/input/package.json @@ -21,7 +21,7 @@ "peerDependencies": { "@mui/icons-material": "^5.10", "@mui/material": "^5.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", + "@ui-schema/kit-codemirror": "0.1.0-alpha.1 || ~0.1.1", "react-progress-state": "~0.2.6 || ~0.3.1", "@ui-controls/progress": "~0.0.4", "@content-ui/react": "*", @@ -33,7 +33,7 @@ "devDependencies": { "@mui/icons-material": "^5.10", "@mui/material": "^5.1", - "@ui-schema/kit-codemirror": "~0.1.0-alpha.1", + "@ui-schema/kit-codemirror": "~0.1.1", "react-progress-state": "~0.3.1", "@ui-controls/progress": "~0.0.4", "@content-ui/react": "*", From 161126b1aa937a75fed9f755740f299c7722a635 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 4 Oct 2024 14:40:02 +0200 Subject: [PATCH 2/6] refac contexts, providers and hooks; fix textToId colon, trailing dot; --- README.md | 4 +- apps/demo/src/App.tsx | 2 +- apps/demo/src/components/ContentUI.ts | 2 +- apps/demo/src/components/CustomCodeMirror.tsx | 4 +- .../CustomWidgets/WidgetMarkdownEditor.tsx | 34 +-- apps/demo/src/components/useHighlightStyle.ts | 131 ---------- apps/demo/src/pages/PageComplex.tsx | 19 +- apps/demo/src/pages/PageInput.tsx | 105 ++++---- apps/demo/tests/PageHome.test.tsx | 2 +- .../src/components/CustomCodeMirror.tsx | 4 +- .../src/components/useHighlightStyle.ts | 142 ----------- apps/sandbox/src/main.tsx | 2 +- apps/sandbox/src/pages/PageInput.tsx | 227 +++++++++--------- babelImportDefaultPlugin.js | 1 - packages/input/ContentInput.tsx | 5 +- packages/input/useContentEditor.ts | 8 +- packages/md-mui/LeafChildNodes.tsx | 4 +- packages/md-mui/Leafs/HTMLLeafs.tsx | 2 +- packages/md-mui/Leafs/LeafBlockquote.tsx | 2 +- packages/md-mui/Leafs/LeafCode.tsx | 4 +- packages/md-mui/Leafs/LeafDefList.tsx | 2 +- packages/md-mui/Leafs/LeafFootnote.tsx | 2 +- packages/md-mui/Leafs/LeafImage.tsx | 2 +- packages/md-mui/Leafs/LeafList.tsx | 2 +- packages/md-mui/Leafs/LeafTable.tsx | 2 +- packages/md-mui/Leafs/LeafToc.tsx | 18 +- packages/md-mui/Leafs/LeafTypo.tsx | 2 +- packages/md-mui/Leafs/LeafYaml.tsx | 2 +- packages/md-mui/LeafsMarkdown.ts | 2 +- packages/md-mui/Renderer.tsx | 7 +- packages/md-mui/Viewer.tsx | 11 +- packages/md-mui/tests/ViewerFromText.test.tsx | 2 +- ...ileProvider.tsx => ContentFileContext.tsx} | 17 +- packages/react/ContentLeaf.tsx | 173 +------------ packages/react/ContentLeafsContext.tsx | 166 +++++++++++++ packages/react/ContentSelectionContext.tsx | 39 +++ packages/react/isLeafSelected.ts | 2 +- packages/react/package.json | 14 +- packages/react/useContent.ts | 42 +--- packages/struct/textToId.ts | 8 +- server/feed/src/handler/ReactHandler.tsx | 5 +- 41 files changed, 493 insertions(+), 731 deletions(-) delete mode 100644 apps/demo/src/components/useHighlightStyle.ts delete mode 100644 apps/sandbox/src/components/useHighlightStyle.ts rename packages/react/{ContentFileProvider.tsx => ContentFileContext.tsx} (53%) create mode 100644 packages/react/ContentLeafsContext.tsx create mode 100644 packages/react/ContentSelectionContext.tsx diff --git a/README.md b/README.md index 309436d..6f03a95 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ - structural helper utils and mdast typing extensions ```shell -npm i -S @content-ui/md @content-ui/react @content-ui/md-mui +npm i -S @content-ui/md @content-ui/react @content-ui/struct @content-ui/md-mui # peer-dependencies: npm i -S @mui/material @mui/icons-material npm i -D @types/mdast # input component with CodeMirror: -npm i -S @content-ui/md @content-ui/react @content-ui/md-mui @content-ui/input +npm i -S @content-ui/md @content-ui/react @content-ui/struct @content-ui/md-mui @content-ui/input # peer-dependencies: npm i -S react-progress-state @ui-controls/progress @ui-schema/kit-codemirror @codemirror/state ``` diff --git a/apps/demo/src/App.tsx b/apps/demo/src/App.tsx index 3c038f9..e45fad3 100644 --- a/apps/demo/src/App.tsx +++ b/apps/demo/src/App.tsx @@ -1,4 +1,4 @@ -import { contentUIDecorators, ContentLeafsProvider } from '@content-ui/react/ContentLeaf' +import { contentUIDecorators, ContentLeafsProvider } from '@content-ui/react/ContentLeafsContext' import React from 'react' import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles' import { DndProvider } from 'react-dnd' diff --git a/apps/demo/src/components/ContentUI.ts b/apps/demo/src/components/ContentUI.ts index 949b384..45d9034 100644 --- a/apps/demo/src/components/ContentUI.ts +++ b/apps/demo/src/components/ContentUI.ts @@ -1,5 +1,5 @@ import { MuiContentRenderComponents, renderMapping } from '@content-ui/md-mui/LeafsMarkdown' -import { ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeaf' +import { ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeafsContext' import { CustomCodeMirror } from './CustomCodeMirror.js' export const contentUIMapping: LeafsRenderMapping = { diff --git a/apps/demo/src/components/CustomCodeMirror.tsx b/apps/demo/src/components/CustomCodeMirror.tsx index a8bd266..0770303 100644 --- a/apps/demo/src/components/CustomCodeMirror.tsx +++ b/apps/demo/src/components/CustomCodeMirror.tsx @@ -19,7 +19,7 @@ import { lintKeymap } from '@codemirror/lint' import { Compartment, EditorState, Extension } from '@codemirror/state' import { CodeMirrorComponentProps, CodeMirrorProps } from '@ui-schema/kit-codemirror/CodeMirror' import { EditorThemeCustomStyles, useEditorTheme } from '@ui-schema/material-code/useEditorTheme' -import { useHighlightStyle } from './useHighlightStyle' +import { useHighlightStyle } from '@ui-schema/material-code/useHighlightStyle' import { json } from '@codemirror/lang-json' import { javascript } from '@codemirror/lang-javascript' import { html } from '@codemirror/lang-html' @@ -257,7 +257,7 @@ export const CustomCodeMirror: React.FC = ( }), [palette.mode, paddingBottom]) const theme = useEditorTheme(typeof onChange === 'undefined', dense, customVariant, customStyles as EditorThemeCustomStyles) - const highlightStyle = useHighlightStyle() + const highlightStyle = useHighlightStyle({headlineUnderline: false}) const {init: initHighlightExt, effects: effectsHighlightExt} = useExtension( () => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}), [highlightStyle], diff --git a/apps/demo/src/components/CustomWidgets/WidgetMarkdownEditor.tsx b/apps/demo/src/components/CustomWidgets/WidgetMarkdownEditor.tsx index effb955..cf3018b 100644 --- a/apps/demo/src/components/CustomWidgets/WidgetMarkdownEditor.tsx +++ b/apps/demo/src/components/CustomWidgets/WidgetMarkdownEditor.tsx @@ -1,4 +1,5 @@ import { ContentParser } from '@content-ui/md/parser/ContentParser' +import { ContentSelectionProvider } from '@content-ui/react/ContentSelectionContext' import { UIMetaReadContextType } from '@ui-schema/ui-schema/UIMetaReadContext' import React from 'react' import { TransTitle, WidgetProps, WithScalarValue } from '@ui-schema/ui-schema' @@ -15,7 +16,7 @@ import { CustomCodeMirror, getHighlight } from '../CustomCodeMirror' import { ContentInput } from '@content-ui/input/ContentInput' import { useContentEditor } from '@content-ui/input/useContentEditor' import { useContent } from '@content-ui/react/useContent' -import { ContentFileProvider } from '@content-ui/react/ContentFileProvider' +import { ContentFileProvider } from '@content-ui/react/ContentFileContext' export const WidgetMarkdownEditor: React.ComponentType = ( { @@ -118,22 +119,23 @@ export const WidgetMarkdownEditor: React.ComponentType - + + + diff --git a/apps/demo/src/components/useHighlightStyle.ts b/apps/demo/src/components/useHighlightStyle.ts deleted file mode 100644 index 7102746..0000000 --- a/apps/demo/src/components/useHighlightStyle.ts +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react' -import { tags } from '@lezer/highlight' -import { HighlightStyle } from '@codemirror/language' -import { useTheme } from '@mui/material/styles' - -export const useHighlightStyle = (): HighlightStyle => { - const {palette} = useTheme() - return React.useMemo(() => HighlightStyle.define([ - { - tag: tags.link, - textDecoration: 'underline', - }, - { - tag: tags.heading, - // textDecoration: 'underline', - color: palette.mode === 'dark' ? '#e4e7e8' : '#011d24', - fontWeight: 'bold', - }, - { - tag: [tags.meta], - color: palette.mode === 'dark' ? '#57b1a8' : '#008074', - }, - { - tag: tags.emphasis, - fontStyle: 'italic', - }, - { - tag: tags.strong, - fontWeight: 'bold', - }, - { - tag: tags.strikethrough, - textDecoration: 'line-through', - }, - { - tag: tags.keyword, - color: palette.mode === 'dark' ? '#d55d9b' : '#c232ab', - }, - { - tag: [tags.atom, tags.bool, tags.null, tags.url, tags.contentSeparator, tags.labelName], - // tags.operator, - color: palette.mode === 'dark' ? '#b167e4' : '#851cce', - }, - { - tag: [tags.literal], // numbers in json+yaml - // tag: [tags.literal, tags.inserted], - color: palette.mode === 'dark' ? '#73a3ce' : '#125f77', - }, - { - tag: [tags.inserted], - // tag: [tags.literal, tags.inserted], - color: palette.mode === 'dark' ? '#1a9544' : '#068248', - }, - { - tag: [tags.deleted], - color: palette.mode === 'dark' ? '#d22c2c' : '#aa1111', - }, - { - tag: [tags.brace], - color: palette.text.secondary, - }, - { - tag: [tags.bracket], - color: palette.mode === 'dark' ? '#608bb1' : '#22758f', - }, - { - tag: [tags.string], - color: palette.mode === 'dark' ? '#83ca69' : '#067326', - }, - { - tag: [tags.regexp, tags.escape, tags.special(tags.string)], - color: palette.mode === 'dark' ? '#ec7242' : '#ee4400', - }, - { - tag: [ - tags.definition(tags.variableName), - // e.g. sass-vars - tags.special(tags.variableName), - tags.variableName, - tags.attributeName, - ], - color: palette.mode === 'dark' ? '#6789ec' : '#1a3ab9', - }, - { - tag: tags.local(tags.variableName), - color: '#3300aa', - }, - { - tag: [tags.typeName, tags.namespace], - color: palette.mode === 'dark' ? '#41aea4' : '#008074', - // color: palette.mode === 'dark' ? '#ec4837' : '#b7382b', - }, - { - tag: tags.className, - // color: '#116677', - color: palette.mode === 'dark' ? '#388c83' : '#207e75', - }, - { - tag: [tags.macroName], - color: '#225566', - }, - /*{ - tag: tags.definition(tags.propertyName), - color: '#0000cc', - },*/ - { - tag: [ - tags.comment, - // tags.blockComment, - ], - color: palette.mode === 'dark' ? '#738284' : '#6b7677', - // backgroundColor: palette.mode === 'dark' ? '#738284' : '#6b7677', - }, - // { - // tag: [ - // tags.blockComment, - // ], - // opacity: 0.75, - // '&:hover, &:active, &:focus': { - // opacity: 1, - // }, - // // backgroundColor: palette.mode === 'dark' ? '#1f2626' : '#e1ebec', - // // display: 'block', - // }, - { - tag: tags.invalid, - color: palette.error.main, - // color: '#ff0000', - }, - ]), [palette]) -} diff --git a/apps/demo/src/pages/PageComplex.tsx b/apps/demo/src/pages/PageComplex.tsx index 289b5d9..42fd095 100644 --- a/apps/demo/src/pages/PageComplex.tsx +++ b/apps/demo/src/pages/PageComplex.tsx @@ -1,4 +1,5 @@ import { ContentParser } from '@content-ui/md/parser/ContentParser' +import { SettingsProvider } from '@content-ui/react/LeafSettings' import Paper from '@mui/material/Paper' import React from 'react' import Helmet from 'react-helmet' @@ -122,12 +123,18 @@ export const PageComplex: React.ComponentType = () => { {contentDetails?.file ? - + + + : null} diff --git a/apps/demo/src/pages/PageInput.tsx b/apps/demo/src/pages/PageInput.tsx index a771513..20b2ea1 100644 --- a/apps/demo/src/pages/PageInput.tsx +++ b/apps/demo/src/pages/PageInput.tsx @@ -1,4 +1,5 @@ import { ContentParser } from '@content-ui/md/parser/ContentParser' +import { ContentSelectionProvider } from '@content-ui/react/ContentSelectionContext' import useMediaQuery from '@mui/material/useMediaQuery' import Button from '@mui/material/Button' import IcVisibility from '@mui/icons-material/Visibility' @@ -15,7 +16,7 @@ import { Viewer } from '@content-ui/md-mui/Viewer' import { SettingsProvider } from '@content-ui/react/LeafSettings' import { useContentEditor } from '@content-ui/input/useContentEditor' import { useContent } from '@content-ui/react/useContent' -import { ContentFileProvider } from '@content-ui/react/ContentFileProvider' +import { ContentFileProvider } from '@content-ui/react/ContentFileContext' const md = `# About a Note @@ -97,60 +98,62 @@ export const PageInput: React.ComponentType = () => { - - - - - - - - - {showAst ? - : null} + + + {showAst ? + : null} + - - + + diff --git a/apps/demo/tests/PageHome.test.tsx b/apps/demo/tests/PageHome.test.tsx index 1377d30..9d60d8f 100644 --- a/apps/demo/tests/PageHome.test.tsx +++ b/apps/demo/tests/PageHome.test.tsx @@ -1,7 +1,7 @@ /** * @jest-environment jsdom */ -import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeaf' +import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeafsContext' import { it, expect, describe } from '@jest/globals' import '@testing-library/jest-dom/jest-globals' import { render, screen } from '@testing-library/react' diff --git a/apps/sandbox/src/components/CustomCodeMirror.tsx b/apps/sandbox/src/components/CustomCodeMirror.tsx index cc24c65..21e3624 100644 --- a/apps/sandbox/src/components/CustomCodeMirror.tsx +++ b/apps/sandbox/src/components/CustomCodeMirror.tsx @@ -50,7 +50,7 @@ import { EditorThemeCustomStyles, useEditorTheme, } from "@ui-schema/material-code/useEditorTheme"; -import { useHighlightStyle } from "./useHighlightStyle"; +import { useHighlightStyle } from "@ui-schema/material-code/useHighlightStyle"; import { json } from "@codemirror/lang-json"; import { javascript } from "@codemirror/lang-javascript"; import { html } from "@codemirror/lang-html"; @@ -343,7 +343,7 @@ export const CustomCodeMirror: React.FC = ({ customStyles as EditorThemeCustomStyles, ); - const highlightStyle = useHighlightStyle(); + const highlightStyle = useHighlightStyle({headlineUnderline: false}); const { init: initHighlightExt, effects: effectsHighlightExt } = useExtension( () => syntaxHighlighting(highlightStyle || defaultHighlightStyle, { diff --git a/apps/sandbox/src/components/useHighlightStyle.ts b/apps/sandbox/src/components/useHighlightStyle.ts deleted file mode 100644 index 9d15b99..0000000 --- a/apps/sandbox/src/components/useHighlightStyle.ts +++ /dev/null @@ -1,142 +0,0 @@ -import React from "react"; -import { tags } from "@lezer/highlight"; -import { HighlightStyle } from "@codemirror/language"; -import useTheme from "@mui/material/styles/useTheme"; - -export const useHighlightStyle = (): HighlightStyle => { - const { palette } = useTheme(); - return React.useMemo( - () => - HighlightStyle.define([ - { - tag: tags.link, - textDecoration: "underline", - }, - { - tag: tags.heading, - // textDecoration: 'underline', - color: palette.mode === "dark" ? "#e4e7e8" : "#011d24", - fontWeight: "bold", - }, - { - tag: [tags.meta], - color: palette.mode === "dark" ? "#57b1a8" : "#008074", - }, - { - tag: tags.emphasis, - fontStyle: "italic", - }, - { - tag: tags.strong, - fontWeight: "bold", - }, - { - tag: tags.strikethrough, - textDecoration: "line-through", - }, - { - tag: tags.keyword, - color: palette.mode === "dark" ? "#d55d9b" : "#c232ab", - }, - { - tag: [ - tags.atom, - tags.bool, - tags.null, - tags.url, - tags.contentSeparator, - tags.labelName, - ], - // tags.operator, - color: palette.mode === "dark" ? "#b167e4" : "#851cce", - }, - { - tag: [tags.literal], // numbers in json+yaml - // tag: [tags.literal, tags.inserted], - color: palette.mode === "dark" ? "#73a3ce" : "#125f77", - }, - { - tag: [tags.inserted], - // tag: [tags.literal, tags.inserted], - color: palette.mode === "dark" ? "#1a9544" : "#068248", - }, - { - tag: [tags.deleted], - color: palette.mode === "dark" ? "#d22c2c" : "#aa1111", - }, - { - tag: [tags.brace], - color: palette.text.secondary, - }, - { - tag: [tags.bracket], - color: palette.mode === "dark" ? "#608bb1" : "#22758f", - }, - { - tag: [tags.string], - color: palette.mode === "dark" ? "#83ca69" : "#067326", - }, - { - tag: [tags.regexp, tags.escape, tags.special(tags.string)], - color: palette.mode === "dark" ? "#ec7242" : "#ee4400", - }, - { - tag: [ - tags.definition(tags.variableName), - // e.g. sass-vars - tags.special(tags.variableName), - tags.variableName, - tags.attributeName, - ], - color: palette.mode === "dark" ? "#6789ec" : "#1a3ab9", - }, - { - tag: tags.local(tags.variableName), - color: "#3300aa", - }, - { - tag: [tags.typeName, tags.namespace], - color: palette.mode === "dark" ? "#41aea4" : "#008074", - // color: palette.mode === 'dark' ? '#ec4837' : '#b7382b', - }, - { - tag: tags.className, - // color: '#116677', - color: palette.mode === "dark" ? "#388c83" : "#207e75", - }, - { - tag: [tags.macroName], - color: "#225566", - }, - /*{ - tag: tags.definition(tags.propertyName), - color: '#0000cc', - },*/ - { - tag: [ - tags.comment, - // tags.blockComment, - ], - color: palette.mode === "dark" ? "#738284" : "#6b7677", - // backgroundColor: palette.mode === 'dark' ? '#738284' : '#6b7677', - }, - // { - // tag: [ - // tags.blockComment, - // ], - // opacity: 0.75, - // '&:hover, &:active, &:focus': { - // opacity: 1, - // }, - // // backgroundColor: palette.mode === 'dark' ? '#1f2626' : '#e1ebec', - // // display: 'block', - // }, - { - tag: tags.invalid, - color: palette.error.main, - // color: '#ff0000', - }, - ]), - [palette], - ); -}; diff --git a/apps/sandbox/src/main.tsx b/apps/sandbox/src/main.tsx index 3134e41..2caa75a 100644 --- a/apps/sandbox/src/main.tsx +++ b/apps/sandbox/src/main.tsx @@ -5,7 +5,7 @@ import { LeafsRenderMapping, ContentLeafsNodeMapping, ContentLeafMatchParams, -} from '@content-ui/react/ContentLeaf' +} from '@content-ui/react/ContentLeafsContext' import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles' import CssBaseline from '@mui/material/CssBaseline' import { MuiContentRenderComponents, renderMapping } from '@content-ui/md-mui/LeafsMarkdown' diff --git a/apps/sandbox/src/pages/PageInput.tsx b/apps/sandbox/src/pages/PageInput.tsx index cdeb437..89cd99f 100644 --- a/apps/sandbox/src/pages/PageInput.tsx +++ b/apps/sandbox/src/pages/PageInput.tsx @@ -1,16 +1,17 @@ -import { ContentParser } from "@content-ui/md/parser/ContentParser"; -import React from "react"; -import Grid2 from "@mui/material/Unstable_Grid2"; -import { ContentInput } from "@content-ui/input/ContentInput"; -import { CustomCodeMirror, getHighlight } from "../components/CustomCodeMirror"; -import Box from "@mui/material/Box"; -import { Viewer } from "@content-ui/md-mui/Viewer"; -import { SettingsProvider } from "@content-ui/react/LeafSettings"; -import { useContentEditor } from "@content-ui/input/useContentEditor"; -import { useContent } from "@content-ui/react/useContent"; -import { ContentFileProvider } from "@content-ui/react/ContentFileProvider"; -import useTheme from "@mui/material/styles/useTheme"; -import { useMediaQuery } from "@mui/material"; +import { ContentParser } from '@content-ui/md/parser/ContentParser' +import { ContentSelectionProvider } from '@content-ui/react/ContentSelectionContext' +import React from 'react' +import Grid2 from '@mui/material/Unstable_Grid2' +import { ContentInput } from '@content-ui/input/ContentInput' +import { CustomCodeMirror, getHighlight } from '../components/CustomCodeMirror' +import Box from '@mui/material/Box' +import { Viewer } from '@content-ui/md-mui/Viewer' +import { SettingsProvider } from '@content-ui/react/LeafSettings' +import { useContentEditor } from '@content-ui/input/useContentEditor' +import { useContent } from '@content-ui/react/useContent' +import { ContentFileProvider } from '@content-ui/react/ContentFileContext' +import useTheme from '@mui/material/styles/useTheme' +import { useMediaQuery } from '@mui/material' const md = `# About a Note @@ -44,107 +45,109 @@ With even more sentences, words and other things. | 6pm | Evening | | 10pm | Night | -`; +` export const PageInput: React.ComponentType = () => { - const [value, setValue] = React.useState(md); - const { breakpoints } = useTheme(); - const isMediumScreen = useMediaQuery(breakpoints.up("md")); - const { - textValue, - handleOnChange, - editorSelection, - bigSize, - autoProcess, - setAutoProcess, - } = useContentEditor(typeof value === "string" ? value : "", setValue); - const { processing, outdated, root, file } = useContent({ - textValue, - // for direct preview, the parseDelay should be as low as possible, - // with disabled preview it's better to use `600` for less unnecessary processing - parseDelay: - textValue.length > 10000 - ? 460 - : textValue.length > 1200 - ? 160 - : textValue.length > 3500 - ? 280 - : 40, - autoProcess, - onMount: true, - processor: ContentParser, - }); + const [value, setValue] = React.useState(md) + const {breakpoints} = useTheme() + const isMediumScreen = useMediaQuery(breakpoints.up('md')) + const { + textValue, + handleOnChange, + editorSelection, + bigSize, + autoProcess, + setAutoProcess, + } = useContentEditor(typeof value === 'string' ? value : '', setValue) + const {processing, outdated, root, file} = useContent({ + textValue, + // for direct preview, the parseDelay should be as low as possible, + // with disabled preview it's better to use `600` for less unnecessary processing + parseDelay: + textValue.length > 10000 + ? 460 + : textValue.length > 1200 + ? 160 + : textValue.length > 3500 + ? 280 + : 40, + autoProcess, + onMount: true, + processor: ContentParser, + }) - const extensions = React.useMemo(() => { - const highlight = getHighlight("md"); - return [...(highlight ? [highlight] : [])]; - }, []); + const extensions = React.useMemo(() => { + const highlight = getHighlight('md') + return [...(highlight ? [highlight] : [])] + }, []) - return ( - <> - - - - + - - - - - - - - - - - - ); -}; + + + + + + + + + + + + + + + + + ) +} diff --git a/babelImportDefaultPlugin.js b/babelImportDefaultPlugin.js index c628e1e..6846c45 100644 --- a/babelImportDefaultPlugin.js +++ b/babelImportDefaultPlugin.js @@ -57,7 +57,6 @@ export default function({types: t}) { } function isLikelyCommonJS(source, state) { - // Log the check // Check if the source is from a package with type: module const isModulePackage = state.file.opts.filename.includes('node_modules') && state.file.opts.packageData diff --git a/packages/input/ContentInput.tsx b/packages/input/ContentInput.tsx index e131801..0ca6922 100644 --- a/packages/input/ContentInput.tsx +++ b/packages/input/ContentInput.tsx @@ -1,3 +1,4 @@ +import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import React from 'react' import { CodeMirrorComponentProps } from '@ui-schema/kit-codemirror/CodeMirror' import { Extension } from '@codemirror/state' @@ -10,7 +11,8 @@ import { InputBottomBar } from '@content-ui/input/InputBottomBar' import { IconButtonProgress } from '@ui-controls/progress/IconButtonProgress' import IcAutoProcess from '@mui/icons-material/ModelTraining' import { CodeMirrorOnChange } from '@ui-schema/kit-codemirror/useCodeMirror' -import { useContentContext, useContentSelection, WithContent } from '@content-ui/react/useContent' +import { useContentContext } from '@content-ui/react/ContentFileContext' +import { WithContent } from '@content-ui/react/useContent' import { Viewer, ViewerProps } from '@content-ui/md-mui/Viewer' export interface ViewEditorProps extends Pick, Omit { @@ -52,7 +54,6 @@ export const ContentInput: React.ComponentType : > + editorSelection: ContentSelection | undefined + setEditorSelection: React.Dispatch> lines: number textValue: string @@ -19,7 +19,7 @@ export const useContentEditor = ( textValue: string, onChange: (newValue: string) => void, ): WithContentEditor => { - const [editorSelection, setEditorSelection] = React.useState(undefined) + const [editorSelection, setEditorSelection] = React.useState(undefined) const bigSize = textValue.length > 50000 const [autoProcess, setAutoProcess] = React.useState(bigSize ? 0 : -1) diff --git a/packages/md-mui/LeafChildNodes.tsx b/packages/md-mui/LeafChildNodes.tsx index 375f063..4d2a82b 100644 --- a/packages/md-mui/LeafChildNodes.tsx +++ b/packages/md-mui/LeafChildNodes.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Parents } from 'mdast' -import { useContentSelection } from '@content-ui/react/useContent' +import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import { ContentLeaf } from '@content-ui/react/ContentLeaf' import { isLeafSelected } from '@content-ui/react/isLeafSelected' @@ -24,7 +24,7 @@ export const LeafChildNodes =

( elem={childNext.type} child={childNext} // todo: add support for multiple selections, e.g. multiple lines with unselected lines in between - selected={isLeafSelected(childNext.position, editorSelection?.startLine, editorSelection?.endLine)} + selected={editorSelection ? isLeafSelected(childNext.position, editorSelection.startLine, editorSelection.endLine) : false} isFirst={i === 0} isLast={i === length - 1} />, diff --git a/packages/md-mui/Leafs/HTMLLeafs.tsx b/packages/md-mui/Leafs/HTMLLeafs.tsx index ca8ed5b..bf725a0 100644 --- a/packages/md-mui/Leafs/HTMLLeafs.tsx +++ b/packages/md-mui/Leafs/HTMLLeafs.tsx @@ -1,7 +1,7 @@ import React from 'react' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import Box from '@mui/material/Box' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { WithMdAstChild } from '@content-ui/struct/Ast' export const LeafBr: React.FC = () =>
diff --git a/packages/md-mui/Leafs/LeafBlockquote.tsx b/packages/md-mui/Leafs/LeafBlockquote.tsx index f673780..3e8a50b 100644 --- a/packages/md-mui/Leafs/LeafBlockquote.tsx +++ b/packages/md-mui/Leafs/LeafBlockquote.tsx @@ -1,7 +1,7 @@ import React from 'react' import Box from '@mui/material/Box' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { useLeafFollower } from '@content-ui/react/useLeafFollower' export const LeafBlockquote: React.FC = ({child, selected}) => { diff --git a/packages/md-mui/Leafs/LeafCode.tsx b/packages/md-mui/Leafs/LeafCode.tsx index 41e8d70..be051b8 100644 --- a/packages/md-mui/Leafs/LeafCode.tsx +++ b/packages/md-mui/Leafs/LeafCode.tsx @@ -1,11 +1,11 @@ import React from 'react' import Box from '@mui/material/Box' -import { ContentLeafProps, ContentLeafsPropsMapping, useContentLeafs } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps, ContentLeafsPropsMapping, useContentLeafs } from '@content-ui/react/ContentLeafsContext' import { useLeafFollower } from '@content-ui/react/useLeafFollower' import { useTheme } from '@mui/material/styles' import type { Theme } from '@mui/material/styles' import { TypographyWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' -import { MuiContentRenderComponents } from '../LeafsMarkdown' +import { MuiContentRenderComponents } from '@content-ui/md-mui/LeafsMarkdown' export const LeafCode: React.FC = ({child, selected}) => { const code = child.type === 'code' ? child : undefined diff --git a/packages/md-mui/Leafs/LeafDefList.tsx b/packages/md-mui/Leafs/LeafDefList.tsx index 3fcd31a..f045e21 100644 --- a/packages/md-mui/Leafs/LeafDefList.tsx +++ b/packages/md-mui/Leafs/LeafDefList.tsx @@ -2,7 +2,7 @@ import React from 'react' import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' import { useTheme } from '@mui/material/styles' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { useLeafFollower } from '@content-ui/react/useLeafFollower' diff --git a/packages/md-mui/Leafs/LeafFootnote.tsx b/packages/md-mui/Leafs/LeafFootnote.tsx index 25c2738..723250b 100644 --- a/packages/md-mui/Leafs/LeafFootnote.tsx +++ b/packages/md-mui/Leafs/LeafFootnote.tsx @@ -4,7 +4,7 @@ import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { MuiLink } from '@content-ui/md-mui/MuiComponents/MuiLink' import IcGoTo from '@mui/icons-material/SubdirectoryArrowLeft' import Typography from '@mui/material/Typography' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { useTheme } from '@mui/material/styles' import type { Theme } from '@mui/material/styles' import { TypographyWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' diff --git a/packages/md-mui/Leafs/LeafImage.tsx b/packages/md-mui/Leafs/LeafImage.tsx index d7e97fc..7885fe7 100644 --- a/packages/md-mui/Leafs/LeafImage.tsx +++ b/packages/md-mui/Leafs/LeafImage.tsx @@ -1,6 +1,6 @@ import React from 'react' import Typography from '@mui/material/Typography' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { WithMdAstChild } from '@content-ui/struct/Ast' export const LeafImage: React.FC = ({child}) => diff --git a/packages/md-mui/Leafs/LeafList.tsx b/packages/md-mui/Leafs/LeafList.tsx index cb78e4b..c3d9ee2 100644 --- a/packages/md-mui/Leafs/LeafList.tsx +++ b/packages/md-mui/Leafs/LeafList.tsx @@ -4,7 +4,7 @@ import Box from '@mui/material/Box' import IcChecked from '@mui/icons-material/CheckBox' import IcUnchecked from '@mui/icons-material/CheckBoxOutlineBlank' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' export const LeafList: React.FC> = ({child}) => { const component = child.type === 'list' && child.ordered ? 'ol' : 'ul' diff --git a/packages/md-mui/Leafs/LeafTable.tsx b/packages/md-mui/Leafs/LeafTable.tsx index dc9cf3e..ad89d95 100644 --- a/packages/md-mui/Leafs/LeafTable.tsx +++ b/packages/md-mui/Leafs/LeafTable.tsx @@ -8,7 +8,7 @@ import TableCell from '@mui/material/TableCell' // import { TableCellProps } from '@mui/material/TableCell/TableCell' import TableBody from '@mui/material/TableBody' import { useTheme } from '@mui/material/styles' -import { ContentLeafProps } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { useLeafFollower } from '@content-ui/react/useLeafFollower' export const LeafTable: React.FC = ({child}) => { diff --git a/packages/md-mui/Leafs/LeafToc.tsx b/packages/md-mui/Leafs/LeafToc.tsx index b808e8f..d1b9e8e 100644 --- a/packages/md-mui/Leafs/LeafToc.tsx +++ b/packages/md-mui/Leafs/LeafToc.tsx @@ -1,10 +1,11 @@ +import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import React from 'react' import { Heading, ListItem, Root } from 'mdast' import { MuiLink } from '@content-ui/md-mui/MuiComponents/MuiLink' import Typography from '@mui/material/Typography' import Box from '@mui/material/Box' -import { ContentLeaf, ContentLeafProps, ContentLeafsPropsMapping } from '@content-ui/react/ContentLeaf' -import { EditorSelection } from '@content-ui/react/useContent' +import { ContentLeaf } from '@content-ui/react/ContentLeaf' +import { ContentLeafProps, ContentLeafsPropsMapping } from '@content-ui/react/ContentLeafsContext' import { useSettings } from '@content-ui/react/LeafSettings' import { flattenText } from '@content-ui/struct/flattenText' import { textToId } from '@content-ui/struct/textToId' @@ -15,7 +16,8 @@ import { TocHNode, TocListItem, WithMdAstChild } from '@content-ui/struct/Ast' export const LeafTocListItem: React.FC & { textVariant?: 'body1' | 'body2' | 'caption' }> = ({child, textVariant}) => { const c = child as TocListItem - const {smallList, showLines, editorSelection, onClick} = useToc() + const editorSelection = useContentSelection() + const {smallList, showLines, onClick} = useToc() // const c = child.type === 'tocListItem' ? child : undefined // todo: is injected in `ContentRenderer`, move to props const {headlineLinkable} = useSettings() @@ -112,7 +114,6 @@ export const LeafTocList: React.FC<{ export interface LeafTocContextType { smallList?: boolean showLines?: boolean - editorSelection?: EditorSelection onClick?: (hNode: TocHNode) => void } @@ -120,10 +121,10 @@ export const LeafTocContext = React.createContext({}) export const useToc = (): LeafTocContextType => React.useContext(LeafTocContext) -export const TocProvider: React.FC> = ({children, showLines, editorSelection, smallList, onClick}) => { +export const TocProvider: React.FC> = ({children, showLines, smallList, onClick}) => { const ctx = React.useMemo( - () => ({showLines, editorSelection, smallList, onClick}), - [showLines, editorSelection, smallList, onClick], + () => ({showLines, smallList, onClick}), + [showLines, smallList, onClick], ) return {children} } @@ -184,7 +185,7 @@ export interface LeafTocProps { export const LeafToc: React.FC = ( { - smallList, showLines, editorSelection, onClick, + smallList, showLines, onClick, tocInject, headlines, }, ) => { @@ -198,7 +199,6 @@ export const LeafToc: React.FC = ( return = ({child}) => { diff --git a/packages/md-mui/LeafsMarkdown.ts b/packages/md-mui/LeafsMarkdown.ts index a260631..d291ecf 100644 --- a/packages/md-mui/LeafsMarkdown.ts +++ b/packages/md-mui/LeafsMarkdown.ts @@ -15,7 +15,7 @@ import { LeafTable, LeafTableCell, LeafTableRow } from '@content-ui/md-mui/Leafs import { LeafYaml } from '@content-ui/md-mui/Leafs/LeafYaml' import { LeafTocListItem } from '@content-ui/md-mui/Leafs/LeafToc' import { LeafImage } from '@content-ui/md-mui/Leafs/LeafImage' -import { ContentRenderComponents, ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeaf' +import { ContentRenderComponents, ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeafsContext' import { LeafDefList, LeafDefListDescription, LeafDefListTerm } from '@content-ui/md-mui/Leafs/LeafDefList' import { ComponentType } from 'react' diff --git a/packages/md-mui/Renderer.tsx b/packages/md-mui/Renderer.tsx index 2e6d9cd..9b179f6 100644 --- a/packages/md-mui/Renderer.tsx +++ b/packages/md-mui/Renderer.tsx @@ -1,5 +1,6 @@ +import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import { Fragment, memo, ReactNode } from 'react' -import { useContentContext, EditorSelection } from '@content-ui/react/useContent' +import { useContentContext } from '@content-ui/react/ContentFileContext' import Typography from '@mui/material/Typography' import { defaultTocIds, LeafToc, LeafTocContextType, useLeafToc } from '@content-ui/md-mui/Leafs/LeafToc' import { isLeafSelected } from '@content-ui/react/isLeafSelected' @@ -7,12 +8,12 @@ import { ContentLeaf } from '@content-ui/react/ContentLeaf' import { FootnoteSection } from '@content-ui/md-mui/Leafs/LeafFootnoteSection' export interface RendererProps { - editorSelection?: EditorSelection handleTocClick?: LeafTocContextType['onClick'] } -export const Renderer = ({handleTocClick, editorSelection}: RendererProps): ReactNode => { +export const Renderer = ({handleTocClick}: RendererProps): ReactNode => { const {root} = useContentContext() + const editorSelection = useContentSelection() const {headlines, tocInject} = useLeafToc(root, defaultTocIds) const bodyNodes = root?.children?.filter(c => c.type !== 'footnoteDefinition') diff --git a/packages/md-mui/Viewer.tsx b/packages/md-mui/Viewer.tsx index 3043ed0..926a362 100644 --- a/packages/md-mui/Viewer.tsx +++ b/packages/md-mui/Viewer.tsx @@ -1,14 +1,14 @@ import React from 'react' import Box from '@mui/material/Box' -import { ContentProcessor, EditorSelection, useContent, useContentContext, WithContent } from '@content-ui/react/useContent' +import { useContentContext } from '@content-ui/react/ContentFileContext' +import { ContentProcessor, useContent, WithContent } from '@content-ui/react/useContent' import Typography from '@mui/material/Typography' import { useLocation } from 'react-router-dom' import LinearProgress from '@mui/material/LinearProgress' -import { ContentFileProvider } from '@content-ui/react/ContentFileProvider' +import { ContentFileProvider } from '@content-ui/react/ContentFileContext' import { RendererMemo } from '@content-ui/md-mui/Renderer' export interface ViewerProps { - editorSelection?: EditorSelection outdated?: boolean processing: WithContent['processing'] m?: number @@ -29,7 +29,6 @@ export interface ViewerProps { export const Viewer =

( { - editorSelection, processing, outdated, ...props @@ -56,7 +55,6 @@ export const Viewer =

( {processing === 'success' || isReady ? : null} {(processing === 'loading' || outdated) && !isReady ? @@ -80,7 +78,7 @@ export interface ViewerFromTextProps extends Omit = ( { textValue, - editorSelection, processor, + processor, parseDelay, onMount = false, ...props @@ -98,7 +96,6 @@ export const ViewerFromText: React.ComponentType = ( file={file} > ({}) + +export const useContentContext = () => React.useContext(ContentContext) + export const ContentFileProvider: React.FC> = ({root, file, editorSelection, children}) => { +}>> = ({root, file, children}) => { const cmCtx = React.useMemo((): ContentFileContextType => ({ root, file, }), [root, file]) return - - {children} - + {children} } diff --git a/packages/react/ContentLeaf.tsx b/packages/react/ContentLeaf.tsx index fe720b3..8b2604d 100644 --- a/packages/react/ContentLeaf.tsx +++ b/packages/react/ContentLeaf.tsx @@ -1,169 +1,10 @@ -import { RootContent } from 'mdast' -import React, { useMemo, memo, createContext, useContext } from 'react' -import { EditorSelection } from '@content-ui/react/useContent' -import { useSettings } from '@content-ui/react/LeafSettings' +import { ReactElement } from 'react' import { DecoratorProps, DecoratorPropsNext, ReactBaseDecorator, ReactDeco } from '@content-ui/react/EngineDecorator' - -export type GenericLeafsDataSpec = { - [k: string]: D -} - -export interface LeafsRenderMapping< - TLeafsMapping extends {} = {}, - TComponentsMapping extends {} = {}, - /** - * The match params should be wider than the params each leaf expects, - * to improve portability of matcher to work with similar leafs, - * as mostly the matcher should work for more leafs than initially known. - * - * @example - * if some leaf param is: `{ elem: 'headline' | 'input' }` - * the match params should be: `{ elem: string }` - */ - TMatchParams extends {} = {}, - /** - * @experimental - */ - TMatchResult = any, - /** - * @experimental - */ - THooks extends {} = {} -> { - components: TComponentsMapping - leafs: TLeafsMapping - /** - * Responsible to match leafs of this mapping. - */ - matchLeaf:

(params: P, leafs: TLeafsMapping) => TMatchResult - children?: never - hooks?: THooks -} - -/** - * A wider `React.ComponentType`, as the remapping had a lot of issues when `React.ComponentType` was used internally, somehow not reproducible here or in others with React18. - * But in ui-schema with the latest React 18 setup, `React.ComponentType` won't work without the `React.ComponentClass

` - */ -export type ReactLeafDefaultNodeType

= React.ComponentClass

| ((props: P, context?: any) => React.ReactNode) -export type ReactLeafsNodeSpec = { - [K in keyof LDS]: ReactLeafDefaultNodeType>; -} - -export type ContentLeafMatchParams = { elem: string } - -export interface LeafsEngine, TRender extends {}> { - renderMap: TRender - deco?: TDeco -} - -export interface ContentLeafPayload { - elem: string - selection?: EditorSelection | undefined - selected?: boolean - // `true` when first Leaf inside the parent level - isFirst?: boolean - // `true` when last Leaf inside the parent level - isLast?: boolean -} - -type MdAstNodes = RootContent - -/** - * @todo make generic and easy to add further mdast types - */ -export type ContentLeafsPropsMapping = { - // [K in CustomMdAstContent['type']]: { elem: K, child: CustomMdAstContent } & ContentLeafPayload - // [K in Content['type']]: { elem: K, child: Content extends { type: K } ? Extract : never } & ContentLeafPayload - [K in MdAstNodes['type']]: { elem: K, child: Extract } & ContentLeafPayload -} - -export type ContentLeafsNodeMapping = ReactLeafsNodeSpec - -export type ContentRenderComponents = {} - -export type ContentLeafProps = ContentLeafsPropsMapping[S] - -export type ContentRendererProps = { - renderMap: LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> - elem: string -} - -export function ContentRenderer

( - { - renderMap, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - next, - ...p - }: P & ContentRendererProps, -): React.ReactElement

| null { - const settings = useSettings() - const leafs = renderMap.leafs - const Leaf = renderMap.matchLeaf(p, leafs) - - if(!Leaf) { - console.error('No LeafNode found for ' + p.elem, p) - return null - } - - return -} - -export const ContentRendererMemo = memo(ContentRenderer) - -export const contentUIDecorators = new ReactDeco< - DecoratorPropsNext & - ContentRendererProps ->() - .use(ContentRendererMemo as typeof ContentRenderer) - -export const contentLeafsContext: React.Context< - LeafsEngine< - ReactDeco<{}, {}>, - LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> - > -> = createContext(undefined as any) - -export const useContentLeafs = < - TLeafsDataMapping extends ContentLeafsPropsMapping = ContentLeafsPropsMapping, - TComponents extends ContentRenderComponents = ContentRenderComponents, - TDeco extends ReactDeco<{}, {}> = ReactDeco<{}, {}>, - TRender extends LeafsRenderMapping, TComponents, ContentLeafMatchParams> = LeafsRenderMapping, TComponents, ContentLeafMatchParams>, ->() => { - return useContext>( - contentLeafsContext as unknown as React.Context>, - ) -} - -export function ContentLeafsProvider< - TLeafsDataMapping extends ContentLeafsPropsMapping = ContentLeafsPropsMapping, - TComponents extends ContentRenderComponents = ContentRenderComponents, - TDeco extends ReactDeco<{}, {}, {}> = ReactDeco<{}, {}, {}>, - TRender extends LeafsRenderMapping, TComponents, ContentLeafMatchParams> = LeafsRenderMapping, TComponents, ContentLeafMatchParams>, - // todo: integrate a typing which validates that the provided deco-result-props are compatible with props of `TRender2['leafs']` ->( - { - children, - deco, renderMap, - }: React.PropsWithChildren>, -) { - const ctx = useMemo((): LeafsEngine => ({ - deco: deco, - renderMap: renderMap, - }), [deco, renderMap]) - - const LeafsContextProvider = contentLeafsContext.Provider - return , TComponents, ContentLeafMatchParams, any, {}> - > - } - >{children} -} +import { + ContentLeafMatchParams, ContentLeafsPropsMapping, ContentRenderComponents, + LeafsEngine, LeafsRenderMapping, ReactLeafsNodeSpec, + useContentLeafs, +} from '@content-ui/react/ContentLeafsContext' export type ContentLeafInjected = 'decoIndex' | 'next' | keyof LeafsEngine @@ -175,7 +16,7 @@ export function ContentLeaf< TProps extends DecoratorProps = DecoratorProps, >( props: Omit, -): React.JSX.Element | null { +): ReactElement | null { const {renderMap, deco} = useContentLeafs< TLeafDataMapping, ContentRenderComponents, TDeco, LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> diff --git a/packages/react/ContentLeafsContext.tsx b/packages/react/ContentLeafsContext.tsx new file mode 100644 index 0000000..56a90a0 --- /dev/null +++ b/packages/react/ContentLeafsContext.tsx @@ -0,0 +1,166 @@ +import { RootContent } from 'mdast' +import React, { useMemo, memo, createContext, useContext } from 'react' +import { useSettings } from '@content-ui/react/LeafSettings' +import { DecoratorPropsNext, ReactDeco } from '@content-ui/react/EngineDecorator' +import { ContentSelection } from '@content-ui/react/ContentSelectionContext' + +export type GenericLeafsDataSpec = { + [k: string]: D +} + +export interface LeafsRenderMapping< + TLeafsMapping extends {} = {}, + TComponentsMapping extends {} = {}, + /** + * The match params should be wider than the params each leaf expects, + * to improve portability of matcher to work with similar leafs, + * as mostly the matcher should work for more leafs than initially known. + * + * @example + * if some leaf param is: `{ elem: 'headline' | 'input' }` + * the match params should be: `{ elem: string }` + */ + TMatchParams extends {} = {}, + /** + * @experimental + */ + TMatchResult = any, + /** + * @experimental + */ + THooks extends {} = {} +> { + components: TComponentsMapping + leafs: TLeafsMapping + /** + * Responsible to match leafs of this mapping. + */ + matchLeaf:

(params: P, leafs: TLeafsMapping) => TMatchResult + children?: never + hooks?: THooks +} + +/** + * A wider `React.ComponentType`, as the remapping had a lot of issues when `React.ComponentType` was used internally, somehow not reproducible here or in others with React18. + * But in ui-schema with the latest React 18 setup, `React.ComponentType` won't work without the `React.ComponentClass

` + */ +export type ReactLeafDefaultNodeType

= React.ComponentClass

| ((props: P, context?: any) => React.ReactNode) +export type ReactLeafsNodeSpec = { + [K in keyof LDS]: ReactLeafDefaultNodeType>; +} + +export type ContentLeafMatchParams = { elem: string } + +export interface LeafsEngine, TRender extends {}> { + renderMap: TRender + deco?: TDeco +} + +export interface ContentLeafPayload { + elem: string + selection?: ContentSelection + selected?: boolean + // `true` when first Leaf inside the parent level + isFirst?: boolean + // `true` when last Leaf inside the parent level + isLast?: boolean +} + +type MdAstNodes = RootContent + +/** + * @todo make generic and easy to add further mdast types + */ +export type ContentLeafsPropsMapping = { + // [K in CustomMdAstContent['type']]: { elem: K, child: CustomMdAstContent } & ContentLeafPayload + // [K in Content['type']]: { elem: K, child: Content extends { type: K } ? Extract : never } & ContentLeafPayload + [K in MdAstNodes['type']]: { elem: K, child: Extract } & ContentLeafPayload +} + +export type ContentLeafsNodeMapping = ReactLeafsNodeSpec + +export type ContentRenderComponents = {} + +export type ContentLeafProps = ContentLeafsPropsMapping[S] + +export type ContentRendererProps = { + renderMap: LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> + elem: string +} + +export function ContentRenderer

( + { + renderMap, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + next, + ...p + }: P & ContentRendererProps, +): React.ReactElement

| null { + const settings = useSettings() + const leafs = renderMap.leafs + const Leaf = renderMap.matchLeaf(p, leafs) + + if(!Leaf) { + console.error('No LeafNode found for ' + p.elem, p) + return null + } + + return +} + +export const ContentRendererMemo = memo(ContentRenderer) + +export const contentUIDecorators = new ReactDeco< + DecoratorPropsNext & + ContentRendererProps +>() + .use(ContentRendererMemo as typeof ContentRenderer) + +export const contentLeafsContext: React.Context< + LeafsEngine< + ReactDeco<{}, {}>, + LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> + > +> = createContext(undefined as any) + +export const useContentLeafs = < + TLeafsDataMapping extends ContentLeafsPropsMapping = ContentLeafsPropsMapping, + TComponents extends ContentRenderComponents = ContentRenderComponents, + TDeco extends ReactDeco<{}, {}> = ReactDeco<{}, {}>, + TRender extends LeafsRenderMapping, TComponents, ContentLeafMatchParams> = LeafsRenderMapping, TComponents, ContentLeafMatchParams>, +>() => { + return useContext>( + contentLeafsContext as unknown as React.Context>, + ) +} + +export function ContentLeafsProvider< + TLeafsDataMapping extends ContentLeafsPropsMapping = ContentLeafsPropsMapping, + TComponents extends ContentRenderComponents = ContentRenderComponents, + TDeco extends ReactDeco<{}, {}, {}> = ReactDeco<{}, {}, {}>, + TRender extends LeafsRenderMapping, TComponents, ContentLeafMatchParams> = LeafsRenderMapping, TComponents, ContentLeafMatchParams>, + // todo: integrate a typing which validates that the provided deco-result-props are compatible with props of `TRender2['leafs']` +>( + { + children, + deco, renderMap, + }: React.PropsWithChildren>, +) { + const ctx = useMemo((): LeafsEngine => ({ + deco: deco, + renderMap: renderMap, + }), [deco, renderMap]) + + const LeafsContextProvider = contentLeafsContext.Provider + return , TComponents, ContentLeafMatchParams, any, {}> + > + } + >{children} +} diff --git a/packages/react/ContentSelectionContext.tsx b/packages/react/ContentSelectionContext.tsx new file mode 100644 index 0000000..66ef6af --- /dev/null +++ b/packages/react/ContentSelectionContext.tsx @@ -0,0 +1,39 @@ +import React, { createContext, useContext } from 'react' + +export interface EditorSelectionPosition { + start: number + startLine: number + startLineStart: number + startLineEnd: number + + end: number + endLine: number + endLineStart: number + endLineEnd: number +} + +export interface EditorSelectionFilled extends EditorSelectionPosition { + selected: true +} + +export interface EditorSelectionEmpty extends Partial { + selected?: false +} + +export type ContentSelection = EditorSelectionEmpty | EditorSelectionFilled +/** + * @deprecated use `ContentSelection` instead + */ +export type EditorSelection = ContentSelection + +export const ContentSelectionContext = createContext(undefined) + +export const useContentSelection = () => useContext(ContentSelectionContext) + +export const ContentSelectionProvider: React.FC> = ({children, selection}) => { + return + {children} + +} diff --git a/packages/react/isLeafSelected.ts b/packages/react/isLeafSelected.ts index a26afd8..1127511 100644 --- a/packages/react/isLeafSelected.ts +++ b/packages/react/isLeafSelected.ts @@ -2,7 +2,7 @@ export const isLeafSelected = ( position: { start: { line: number }, end: { line: number } } | undefined, startLine: number | undefined, endLine: number | undefined, -) => +): boolean => typeof position?.start.line === 'number' && typeof startLine === 'number' && typeof endLine === 'number' && ( diff --git a/packages/react/package.json b/packages/react/package.json index 764ed99..697887f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -24,14 +24,22 @@ "import": "./Utils/copyToClipboard.js", "types": "./Utils/copyToClipboard.d.ts" }, - "./ContentFileProvider": { - "import": "./ContentFileProvider.js", - "types": "./ContentFileProvider.d.ts" + "./ContentFileContext": { + "import": "./ContentFileContext.js", + "types": "./ContentFileContext.d.ts" }, "./ContentLeaf": { "import": "./ContentLeaf.js", "types": "./ContentLeaf.d.ts" }, + "./ContentLeafsContext": { + "import": "./ContentLeafsContext.js", + "types": "./ContentLeafsContext.d.ts" + }, + "./ContentSelectionContext": { + "import": "./ContentSelectionContext.js", + "types": "./ContentSelectionContext.d.ts" + }, "./EngineDecorator": { "import": "./EngineDecorator.js", "types": "./EngineDecorator.d.ts" diff --git a/packages/react/useContent.ts b/packages/react/useContent.ts index ab31fe6..df0639d 100644 --- a/packages/react/useContent.ts +++ b/packages/react/useContent.ts @@ -1,44 +1,8 @@ -import React, { useCallback, useRef, useState } from 'react' +import { useEffect, useCallback, useRef, useState } from 'react' import { Processor } from 'unified' import { VFile } from 'vfile' import { Root } from 'mdast' -export interface EditorSelectionPosition { - start: number - startLine: number - startLineStart: number - startLineEnd: number - - end: number - endLine: number - endLineStart: number - endLineEnd: number -} - -export interface EditorSelectionFilled extends EditorSelectionPosition { - selected: true -} - -export interface EditorSelectionEmpty extends Partial { - selected?: false -} - -export type EditorSelection = EditorSelectionEmpty | EditorSelectionFilled - -export interface ContentFileContextType { - // root?: OrderedMap - root?: Root - file?: VFile -} - -export const ContentContext = React.createContext({}) - -export const useContentContext = () => React.useContext(ContentContext) - -export const ContentSelectionContext = React.createContext(undefined) - -export const useContentSelection = () => React.useContext(ContentSelectionContext) - export type ContentProcessor = Processor export interface WithContent { @@ -151,11 +115,11 @@ export const useContent = ( const delayRefs = useRef({parseDelay, forceAfter}) delayRefs.current = {parseDelay, forceAfter} - React.useEffect(() => { + useEffect(() => { return () => window.clearTimeout(timer2.current) }, []) - React.useEffect(() => { + useEffect(() => { if(onMount && !mountedRef.current) { mountedRef.current = true return diff --git a/packages/struct/textToId.ts b/packages/struct/textToId.ts index f5f0f49..a03495d 100644 --- a/packages/struct/textToId.ts +++ b/packages/struct/textToId.ts @@ -2,11 +2,11 @@ export const textToId = (text: string): string => text .toLowerCase() - // replace any non-word characters (except `:` `.` `-`) with a hyphen - .replace(/[^\w:.-]+/g, '-') + // replace any non-word characters (except `.` `-`) with a hyphen + .replace(/[^\w.-]+/g, '-') // remove maybe generated leading hyphens, including numbers .replace(/^[-\d]+/, '') // replace multiple hyphens with a single one .replace(/-+/g, '-') - // trim any trailing hyphens - .replace(/-$/, '') + // remove trailing hyphens, dots + .replace(/[-.]+$/, '') diff --git a/server/feed/src/handler/ReactHandler.tsx b/server/feed/src/handler/ReactHandler.tsx index 6868f7b..c1d0a65 100644 --- a/server/feed/src/handler/ReactHandler.tsx +++ b/server/feed/src/handler/ReactHandler.tsx @@ -1,7 +1,7 @@ import { Viewer } from '@content-ui/md-mui/Viewer' import { ContentParser } from '@content-ui/md/parser/ContentParser' -import { ContentFileProvider } from '@content-ui/react/ContentFileProvider' -import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeaf' +import { ContentFileProvider } from '@content-ui/react/ContentFileContext' +import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeafsContext' import { Express } from 'express' import { renderToStaticMarkup } from 'react-dom/server' import { StaticRouter } from 'react-router-dom/server' @@ -35,7 +35,6 @@ This is **rendered static on server**. file={file} > From ab0e1d5bf9fe13367939f837e0e463423116dbb2 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 4 Oct 2024 18:24:38 +0200 Subject: [PATCH 3/6] mv isLeafSelected to utils; table component types; --- packages/md-mui/LeafChildNodes.tsx | 2 +- packages/md-mui/Leafs/LeafTable.tsx | 11 +++++------ packages/md-mui/Renderer.tsx | 2 +- packages/react/{ => Utils}/isLeafSelected.ts | 0 packages/react/package.json | 8 ++++---- packages/react/useLeafFollower.ts | 18 ++++++++---------- 6 files changed, 19 insertions(+), 22 deletions(-) rename packages/react/{ => Utils}/isLeafSelected.ts (100%) diff --git a/packages/md-mui/LeafChildNodes.tsx b/packages/md-mui/LeafChildNodes.tsx index 4d2a82b..5b29283 100644 --- a/packages/md-mui/LeafChildNodes.tsx +++ b/packages/md-mui/LeafChildNodes.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Parents } from 'mdast' import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import { ContentLeaf } from '@content-ui/react/ContentLeaf' -import { isLeafSelected } from '@content-ui/react/isLeafSelected' +import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' export const LeafChildNodes =

( props: diff --git a/packages/md-mui/Leafs/LeafTable.tsx b/packages/md-mui/Leafs/LeafTable.tsx index ad89d95..50272ea 100644 --- a/packages/md-mui/Leafs/LeafTable.tsx +++ b/packages/md-mui/Leafs/LeafTable.tsx @@ -5,13 +5,12 @@ import Table from '@mui/material/Table' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import TableCell from '@mui/material/TableCell' -// import { TableCellProps } from '@mui/material/TableCell/TableCell' import TableBody from '@mui/material/TableBody' import { useTheme } from '@mui/material/styles' import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { useLeafFollower } from '@content-ui/react/useLeafFollower' -export const LeafTable: React.FC = ({child}) => { +export const LeafTable: React.FC> = ({child}) => { const c = child as MdTable // todo: align exists on table level, not on cell level //const align = c.align @@ -46,14 +45,14 @@ export const LeafTable: React.FC = ({child}) => { } -export const LeafTableRow: React.FC = ({child, selected, ...props}) => { +export const LeafTableRow: React.FC> = ({child, selected, ...props}) => { const rRef = useLeafFollower(selected) return - {child.type === 'tableRow' ? : null} + } -export const LeafTableCell: React.FC = ({child, selected, ...props}) => { +export const LeafTableCell: React.FC & { tableSettings?: any }> = ({child, selected, ...props}) => { const {palette} = useTheme() // const c = child as MdTableCell // todo: add gfm-advanced support @@ -66,6 +65,6 @@ export const LeafTableCell: React.FC boxShadow: selected ? palette.mode === 'dark' ? '-8px 0px 0px 0px rgba(5, 115, 115, 0.11)' : '-8px 0px 0px 0px rgba(206, 230, 228, 0.31)' : undefined, }} > - {child.type === 'tableCell' ? : null} + } diff --git a/packages/md-mui/Renderer.tsx b/packages/md-mui/Renderer.tsx index 9b179f6..4271235 100644 --- a/packages/md-mui/Renderer.tsx +++ b/packages/md-mui/Renderer.tsx @@ -3,7 +3,7 @@ import { Fragment, memo, ReactNode } from 'react' import { useContentContext } from '@content-ui/react/ContentFileContext' import Typography from '@mui/material/Typography' import { defaultTocIds, LeafToc, LeafTocContextType, useLeafToc } from '@content-ui/md-mui/Leafs/LeafToc' -import { isLeafSelected } from '@content-ui/react/isLeafSelected' +import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' import { ContentLeaf } from '@content-ui/react/ContentLeaf' import { FootnoteSection } from '@content-ui/md-mui/Leafs/LeafFootnoteSection' diff --git a/packages/react/isLeafSelected.ts b/packages/react/Utils/isLeafSelected.ts similarity index 100% rename from packages/react/isLeafSelected.ts rename to packages/react/Utils/isLeafSelected.ts diff --git a/packages/react/package.json b/packages/react/package.json index 697887f..0a47810 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -24,6 +24,10 @@ "import": "./Utils/copyToClipboard.js", "types": "./Utils/copyToClipboard.d.ts" }, + "./Utils/isLeafSelected": { + "import": "./Utils/isLeafSelected.js", + "types": "./Utils/isLeafSelected.d.ts" + }, "./ContentFileContext": { "import": "./ContentFileContext.js", "types": "./ContentFileContext.d.ts" @@ -44,10 +48,6 @@ "import": "./EngineDecorator.js", "types": "./EngineDecorator.d.ts" }, - "./isLeafSelected": { - "import": "./isLeafSelected.js", - "types": "./isLeafSelected.d.ts" - }, "./LeafSettings": { "import": "./LeafSettings.js", "types": "./LeafSettings.d.ts" diff --git a/packages/react/useLeafFollower.ts b/packages/react/useLeafFollower.ts index 76924fa..7735715 100644 --- a/packages/react/useLeafFollower.ts +++ b/packages/react/useLeafFollower.ts @@ -2,19 +2,17 @@ import React from 'react' import { useSettings } from '@content-ui/react/LeafSettings' export const useLeafFollower = (selected: boolean | undefined) => { - const pRef = React.useRef(null) + const elemRef = React.useRef(null) const {followEditor} = useSettings() React.useEffect(() => { - if(!followEditor) return - if(pRef.current && selected) { - pRef.current?.scrollIntoView({ - behavior: 'smooth', - block: 'center', - }) - } - }, [followEditor, selected, pRef]) + if(!followEditor || !selected) return + elemRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }) + }, [followEditor, selected, elemRef]) - return pRef + return elemRef } From 4f20207ac4571300d63d573e1614c5e9b5cfc7ef Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 4 Oct 2024 22:23:52 +0200 Subject: [PATCH 4/6] strict engine/matcher typing; mv selected to deco; strict leafs typings; --- apps/demo/src/components/ContentUI.ts | 5 +- apps/sandbox/src/main.tsx | 11 +-- packages/md-mui/LeafChildNodes.tsx | 16 ++-- packages/md-mui/Leafs/HTMLLeafs.tsx | 44 +++++----- packages/md-mui/Leafs/LeafBlockquote.tsx | 5 +- packages/md-mui/Leafs/LeafCode.tsx | 10 +-- packages/md-mui/Leafs/LeafDefList.tsx | 24 +++--- packages/md-mui/Leafs/LeafFootnote.tsx | 22 +++-- packages/md-mui/Leafs/LeafImage.tsx | 24 +++--- packages/md-mui/Leafs/LeafList.tsx | 34 ++++---- packages/md-mui/Leafs/LeafToc.tsx | 51 ++++++----- packages/md-mui/Leafs/LeafTypo.tsx | 14 ++- packages/md-mui/Leafs/LeafYaml.tsx | 17 ++-- packages/md-mui/LeafsMarkdown.ts | 14 +-- packages/md-mui/Renderer.tsx | 13 ++- packages/react/ContentLeaf.tsx | 6 +- packages/react/ContentLeafsContext.tsx | 105 +++++++++++++++-------- packages/struct/Ast.ts | 46 ++++++++-- server/feed/src/handler/ReactHandler.tsx | 4 +- 19 files changed, 260 insertions(+), 205 deletions(-) diff --git a/apps/demo/src/components/ContentUI.ts b/apps/demo/src/components/ContentUI.ts index 45d9034..670f63e 100644 --- a/apps/demo/src/components/ContentUI.ts +++ b/apps/demo/src/components/ContentUI.ts @@ -1,8 +1,7 @@ -import { MuiContentRenderComponents, renderMapping } from '@content-ui/md-mui/LeafsMarkdown' -import { ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeafsContext' +import { renderMapping } from '@content-ui/md-mui/LeafsMarkdown' import { CustomCodeMirror } from './CustomCodeMirror.js' -export const contentUIMapping: LeafsRenderMapping = { +export const contentUIMapping: typeof renderMapping = { ...renderMapping, leafs: { ...renderMapping.leafs, diff --git a/apps/sandbox/src/main.tsx b/apps/sandbox/src/main.tsx index 2caa75a..1c6ec56 100644 --- a/apps/sandbox/src/main.tsx +++ b/apps/sandbox/src/main.tsx @@ -2,13 +2,10 @@ import React from 'react' import { contentUIDecorators, ContentLeafsProvider, - LeafsRenderMapping, - ContentLeafsNodeMapping, - ContentLeafMatchParams, } from '@content-ui/react/ContentLeafsContext' import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles' import CssBaseline from '@mui/material/CssBaseline' -import { MuiContentRenderComponents, renderMapping } from '@content-ui/md-mui/LeafsMarkdown' +import { renderMapping } from '@content-ui/md-mui/LeafsMarkdown' import { customTheme } from './theme' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' @@ -20,11 +17,7 @@ const root = ReactDOM.createRoot(rootElement) const themes = customTheme() -const contentUIMapping: LeafsRenderMapping< - ContentLeafsNodeMapping, - MuiContentRenderComponents, - ContentLeafMatchParams -> = { +const contentUIMapping: typeof renderMapping = { ...renderMapping, leafs: { ...renderMapping.leafs, diff --git a/packages/md-mui/LeafChildNodes.tsx b/packages/md-mui/LeafChildNodes.tsx index 5b29283..1f53eb3 100644 --- a/packages/md-mui/LeafChildNodes.tsx +++ b/packages/md-mui/LeafChildNodes.tsx @@ -1,18 +1,18 @@ import React from 'react' import { Parents } from 'mdast' -import { useContentSelection } from '@content-ui/react/ContentSelectionContext' +//import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import { ContentLeaf } from '@content-ui/react/ContentLeaf' -import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' +//import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' -export const LeafChildNodes =

( +export const LeafChildNodes =

( props: P & { - childNodes: Parents['children'] + childNodes: TChildNodes }, -) => { +): React.ReactNode => { // todo: move this hook into the decorators - const editorSelection = useContentSelection() + // const editorSelection = useContentSelection() // eslint-disable-next-line @typescript-eslint/no-unused-vars const {childNodes, ...p} = props const length = childNodes.length @@ -24,9 +24,9 @@ export const LeafChildNodes =

( elem={childNext.type} child={childNext} // todo: add support for multiple selections, e.g. multiple lines with unselected lines in between - selected={editorSelection ? isLeafSelected(childNext.position, editorSelection.startLine, editorSelection.endLine) : false} + // selected={editorSelection ? isLeafSelected(childNext.position, editorSelection.startLine, editorSelection.endLine) : false} isFirst={i === 0} isLast={i === length - 1} />, - ) as unknown as React.ReactElement + ) } diff --git a/packages/md-mui/Leafs/HTMLLeafs.tsx b/packages/md-mui/Leafs/HTMLLeafs.tsx index bf725a0..c910291 100644 --- a/packages/md-mui/Leafs/HTMLLeafs.tsx +++ b/packages/md-mui/Leafs/HTMLLeafs.tsx @@ -1,43 +1,41 @@ import React from 'react' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import Box from '@mui/material/Box' -import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' -import { WithMdAstChild } from '@content-ui/struct/Ast' +import { ContentLeafPayload, ContentLeafProps } from '@content-ui/react/ContentLeafsContext' +import { Insert, Mark, Sub, Super, Underline } from '@content-ui/struct/Ast' -export const LeafBr: React.FC = () =>
+export const LeafBr: React.FC> = () =>
-export const LeafText: React.FC & WithMdAstChild> = ({child}): React.ReactNode => ( - child.value -) +export const LeafText: React.FC> = ({child}) => child.value -export const LeafHtml: React.FC & WithMdAstChild> = ({child}): React.ReactNode => ( +export const LeafHtml: React.FC> = ({child}): React.ReactNode => ( child.value.trim().startsWith('') ? {child.value} :

{child.value}
) -export const LeafEmphasis: React.FC = ({child}) => - 'children' in child ? : null +export const LeafEmphasis: React.FC> = ({child}) => + -export const LeafStrong: React.FC = ({child}) => - 'children' in child ? : null +export const LeafStrong: React.FC> = ({child}) => + -export const LeafUnderline: React.FC = ({child}) => - 'children' in child ? : null +export const LeafUnderline: React.FC> = ({child}) => + -export const LeafDelete: React.FC = ({child}) => +export const LeafDelete: React.FC> = ({child}) => 'children' in child ? : null -export const LeafInsert: React.FC = ({child}) => - 'children' in child ? : null +export const LeafInsert: React.FC> = ({child}) => + -export const LeafSub: React.FC = ({child}) => - 'children' in child ? : null +export const LeafSub: React.FC> = ({child}) => + -export const LeafSuper: React.FC = ({child}) => - 'children' in child ? : null +export const LeafSuper: React.FC> = ({child}) => + -export const LeafMark: React.FC = ({child}) => - 'children' in child ? : null +export const LeafMark: React.FC> = ({child}) => + -export const LeafThematicBreak: React.FC = () => +export const LeafThematicBreak: React.FC> = () => diff --git a/packages/md-mui/Leafs/LeafBlockquote.tsx b/packages/md-mui/Leafs/LeafBlockquote.tsx index 3e8a50b..e05fe8f 100644 --- a/packages/md-mui/Leafs/LeafBlockquote.tsx +++ b/packages/md-mui/Leafs/LeafBlockquote.tsx @@ -4,7 +4,7 @@ import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' import { useLeafFollower } from '@content-ui/react/useLeafFollower' -export const LeafBlockquote: React.FC = ({child, selected}) => { +export const LeafBlockquote: React.FC> = ({child, selected}) => { const bRef = useLeafFollower(selected) const classNames = typeof child.data === 'object' @@ -44,8 +44,7 @@ export const LeafBlockquote: React.FC = ({child, selected}) => borderLeftStyle: 'solid', }} > - {child.type === 'blockquote' ? - : null} + } diff --git a/packages/md-mui/Leafs/LeafCode.tsx b/packages/md-mui/Leafs/LeafCode.tsx index be051b8..e2d83b4 100644 --- a/packages/md-mui/Leafs/LeafCode.tsx +++ b/packages/md-mui/Leafs/LeafCode.tsx @@ -7,11 +7,9 @@ import type { Theme } from '@mui/material/styles' import { TypographyWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' import { MuiContentRenderComponents } from '@content-ui/md-mui/LeafsMarkdown' -export const LeafCode: React.FC = ({child, selected}) => { - const code = child.type === 'code' ? child : undefined +export const LeafCode: React.FC> = ({child, selected}) => { const cRef = useLeafFollower(selected) const {renderMap: {components}} = useContentLeafs() - if(child.type !== 'code') return null const Code = components.Code // eslint-disable-next-line deprecation/deprecation @@ -21,13 +19,13 @@ export const LeafCode: React.FC = ({child, selected}) => { {Code ? :
{child.value}
}
} -export const LeafCodeInline: React.FC = ({child}) => { +export const LeafCodeInline: React.FC> = ({child}) => { const {typography} = useTheme() return = ({child}) => { opacity: 0.8, }} > - {child.type === 'inlineCode' ? child.value : null} + {child.value} } diff --git a/packages/md-mui/Leafs/LeafDefList.tsx b/packages/md-mui/Leafs/LeafDefList.tsx index f045e21..ea26cca 100644 --- a/packages/md-mui/Leafs/LeafDefList.tsx +++ b/packages/md-mui/Leafs/LeafDefList.tsx @@ -1,25 +1,29 @@ +import { useSettings } from '@content-ui/react/LeafSettings' +import { DefListDescriptionNode, DefListNode, DefListTermNode } from 'mdast-util-definition-list' import React from 'react' import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' import { useTheme } from '@mui/material/styles' -import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' +import { ContentLeafPayload } from '@content-ui/react/ContentLeafsContext' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { useLeafFollower } from '@content-ui/react/useLeafFollower' -export const LeafDefList: React.FC = ({child}) => { - const dense = 'dense' in child && child.dense +export const LeafDefList: React.FC> = ({child}) => { + const {dense} = useSettings() + // todo: check where the child.dense was used/injected or not all all anymore + const denseApplied = dense || ('dense' in child && child.dense) return - {child.type === 'defList' ? : null} + } -export const LeafDefListTerm: React.FC = ({child, selected}) => { +export const LeafDefListTerm: React.FC> = ({child, selected}) => { const {palette} = useTheme() const dtRef = useLeafFollower(selected) return = ({child, selected}) = boxShadow: selected ? palette.mode === 'dark' ? '-8px 0px 0px 0px rgba(5, 115, 115, 0.11)' : '-8px 0px 0px 0px rgba(206, 230, 228, 0.31)' : undefined, }} > - {child.type === 'defListTerm' ? : null} + } -export const LeafDefListDescription: React.FC = ({child, selected}) => { +export const LeafDefListDescription: React.FC> = ({child, selected}) => { const dtRef = useLeafFollower(selected) return = ({child, selec }} ref={dtRef} > - {child.type === 'defListDescription' ? : null} + } diff --git a/packages/md-mui/Leafs/LeafFootnote.tsx b/packages/md-mui/Leafs/LeafFootnote.tsx index 723250b..f171090 100644 --- a/packages/md-mui/Leafs/LeafFootnote.tsx +++ b/packages/md-mui/Leafs/LeafFootnote.tsx @@ -1,5 +1,4 @@ import React from 'react' -import { FootnoteReference } from 'mdast' import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { MuiLink } from '@content-ui/md-mui/MuiComponents/MuiLink' import IcGoTo from '@mui/icons-material/SubdirectoryArrowLeft' @@ -12,29 +11,28 @@ import { TypographyWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' const userContentPrefix = 'user-content-' export const footnoteContainerId = 'footnote-label' -export const LeafFootnoteDefinition: React.FC = ({child}) => { - const c = child.type === 'footnoteDefinition' ? child : undefined +export const LeafFootnoteDefinition: React.FC> = ({child}) => { const {typography} = useTheme() return
- {c ? + {child ? {'[' + c?.label + ']'} + }}>{'[' + child?.label + ']'} : null} -
- {child.type === 'footnoteDefinition' ? : null} +
+
= ({child}) => { } -export const LeafFootnoteReference: React.FC = ({child}) => { +export const LeafFootnoteReference: React.FC> = ({child}) => { return - {child.type === 'footnoteReference' ? '[' + child.label + ']' : '*'} + {'[' + child.label + ']'} } diff --git a/packages/md-mui/Leafs/LeafImage.tsx b/packages/md-mui/Leafs/LeafImage.tsx index 7885fe7..b1ef937 100644 --- a/packages/md-mui/Leafs/LeafImage.tsx +++ b/packages/md-mui/Leafs/LeafImage.tsx @@ -1,18 +1,16 @@ import React from 'react' import Typography from '@mui/material/Typography' import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' -import { WithMdAstChild } from '@content-ui/struct/Ast' -export const LeafImage: React.FC = ({child}) => - child.type === 'image' ? - - {child.alt +export const LeafImage: React.FC> = ({child}) => + + {child.alt - {child.title ? - {child.title} : null} - : null + {child.title ? + {child.title} : null} + diff --git a/packages/md-mui/Leafs/LeafList.tsx b/packages/md-mui/Leafs/LeafList.tsx index c3d9ee2..0fb9c5d 100644 --- a/packages/md-mui/Leafs/LeafList.tsx +++ b/packages/md-mui/Leafs/LeafList.tsx @@ -7,7 +7,7 @@ import { LeafChildNodes } from '@content-ui/md-mui/LeafChildNodes' import { ContentLeafProps } from '@content-ui/react/ContentLeafsContext' export const LeafList: React.FC> = ({child}) => { - const component = child.type === 'list' && child.ordered ? 'ol' : 'ul' + const component = child.ordered ? 'ol' : 'ul' const dense = 'dense' in child && child.dense const inList = 'inList' in child && child.inList return > = ({child}) => { }} style={{outline: 0, border: 0}} > - {child.type === 'list' ? : null} + } -export const LeafListItem: React.FC = ({child}) => { - const listItemContent = child.type === 'listItem' ? - c.type !== 'list')}/> : null +export const LeafListItem: React.FC> = ({child}) => { + const listItemContent = c.type !== 'list')}/> return - {child.type === 'listItem' && typeof child.checked === 'boolean' ? + {typeof child.checked === 'boolean' ? {child.checked ? : @@ -45,16 +44,15 @@ export const LeafListItem: React.FC = ({child}) => { : listItemContent} - {child.type === 'listItem' ? - c.type === 'list') - .map(c => ({ - ...c, - inList: true, - })) - } - /> : null} + c.type === 'list') + .map(c => ({ + ...c, + inList: true, + })) + } + /> } diff --git a/packages/md-mui/Leafs/LeafToc.tsx b/packages/md-mui/Leafs/LeafToc.tsx index d1b9e8e..29c8be5 100644 --- a/packages/md-mui/Leafs/LeafToc.tsx +++ b/packages/md-mui/Leafs/LeafToc.tsx @@ -5,33 +5,31 @@ import { MuiLink } from '@content-ui/md-mui/MuiComponents/MuiLink' import Typography from '@mui/material/Typography' import Box from '@mui/material/Box' import { ContentLeaf } from '@content-ui/react/ContentLeaf' -import { ContentLeafProps, ContentLeafsPropsMapping } from '@content-ui/react/ContentLeafsContext' +import { ContentLeafPayload, ContentLeafsPropsMapping } from '@content-ui/react/ContentLeafsContext' import { useSettings } from '@content-ui/react/LeafSettings' import { flattenText } from '@content-ui/struct/flattenText' import { textToId } from '@content-ui/struct/textToId' import { useTheme } from '@mui/material/styles' import type { Theme } from '@mui/material/styles' import { TypographyWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' -import { TocHNode, TocListItem, WithMdAstChild } from '@content-ui/struct/Ast' +import { TocHNode, TocListItem } from '@content-ui/struct/Ast' -export const LeafTocListItem: React.FC & { textVariant?: 'body1' | 'body2' | 'caption' }> = ({child, textVariant}) => { - const c = child as TocListItem +export const LeafTocListItem: React.FC & { textVariant?: 'body1' | 'body2' | 'caption' }> = ({child, textVariant}) => { const editorSelection = useContentSelection() const {smallList, showLines, onClick} = useToc() - // const c = child.type === 'tocListItem' ? child : undefined // todo: is injected in `ContentRenderer`, move to props const {headlineLinkable} = useSettings() const {typography} = useTheme() const [focus, setFocus] = React.useState(false) - const selectedByLine = c && ( - editorSelection?.startLine === c?.value.headline.position?.start?.line || - editorSelection?.endLine === c?.value.headline.position?.end?.line + const selectedByLine = child && ( + editorSelection?.startLine === child?.headline.headline.position?.start?.line || + editorSelection?.endLine === child?.headline.headline.position?.end?.line ) - return c ? + return child ? {headlineLinkable ? onClick?.(c?.value)} + onClick={() => onClick?.(child?.headline)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} > - {c.value.flatText.join('')} + {child.headline.flatText.join('')} : - {c.value.flatText.join('')} + {child.headline.flatText.join('')} } {showLines ? @@ -64,21 +62,25 @@ export const LeafTocListItem: React.FC {'L'} - {c.value.headline.position?.start?.line} - {c.value.headline.position?.start?.line !== c.value.headline.position?.end?.line ? - ' to L' + c.value.headline.position?.end?.line : ''} + {child.headline.headline.position?.start?.line} + {child.headline.headline.position?.start?.line !== child.headline.headline.position?.end?.line ? + ' to L' + child.headline.headline.position?.end?.line : ''} : null} - {c?.value.nested?.length || 0 > 0 ? + {/* todo: refactor to use `leafs` */} + {child?.headline.nested?.length || 0 > 0 ? : null} : null } +/** + * @todo refactor as Leaf component + */ export const LeafTocList: React.FC<{ headLines: TocHNode[] depth: number @@ -102,11 +104,11 @@ export const LeafTocList: React.FC<{ type: 'list', ordered: true, dense: dense, - children: sameDepthTree.map((hNode) => ({ - type: 'tocListItem' as 'listItem', - value: hNode, + children: sameDepthTree.map((hNode): TocListItem => ({ + type: 'tocListItem', + headline: hNode, children: [], - })) as ListItem[], + })) as unknown as ListItem[], }} /> } @@ -183,6 +185,9 @@ export interface LeafTocProps { headlines: TocHNode[] | undefined } +/** + * @todo move to components mapping + */ export const LeafToc: React.FC = ( { smallList, showLines, onClick, diff --git a/packages/md-mui/Leafs/LeafTypo.tsx b/packages/md-mui/Leafs/LeafTypo.tsx index 8814259..a28f1c1 100644 --- a/packages/md-mui/Leafs/LeafTypo.tsx +++ b/packages/md-mui/Leafs/LeafTypo.tsx @@ -15,9 +15,8 @@ import { useLeafFollower } from '@content-ui/react/useLeafFollower' import { copyToClipBoard } from '@content-ui/react/Utils/copyToClipboard' import { flattenText } from '@content-ui/struct/flattenText' import { textToId } from '@content-ui/struct/textToId' -import { WithMdAstChild } from '@content-ui/struct/Ast' -export const LeafP: React.FC = ({child, selected, dense, isLast}) => { +export const LeafP: React.FC & { selected?: boolean, dense?: boolean }> = ({child, selected, dense, isLast}) => { const {palette} = useTheme() const pRef = useLeafFollower(selected) return - {child.type === 'paragraph' ? : null} + } -export const LeafH: React.FC = ({child, selected, isFirst, isLast}) => { +export const LeafH: React.FC & { selected?: boolean }> = ({child, selected, isFirst, isLast}) => { const {palette} = useTheme() const { headlineLinkable, @@ -45,8 +44,7 @@ export const LeafH: React.FC(undefined) - const c = child.type === 'heading' ? child : undefined - const id = c ? textToId(flattenText(c as Parent).join('')) : undefined + const id = child ? textToId(flattenText(child as Parent).join('')) : undefined const navigate = useNavigate() React.useEffect(() => { @@ -67,7 +65,7 @@ export const LeafH: React.FC {btnCopy} - {c ? : null} + {child ? : null} } diff --git a/packages/md-mui/Leafs/LeafYaml.tsx b/packages/md-mui/Leafs/LeafYaml.tsx index 71bbd82..5a04a2e 100644 --- a/packages/md-mui/Leafs/LeafYaml.tsx +++ b/packages/md-mui/Leafs/LeafYaml.tsx @@ -9,22 +9,21 @@ import Collapse from '@mui/material/Collapse' import { ContentLeafProps, ContentLeafsPropsMapping, useContentLeafs } from '@content-ui/react/ContentLeafsContext' import { MuiContentRenderComponents } from '@content-ui/md-mui/LeafsMarkdown' -export const LeafYaml: React.FC = ({child}) => { +export const LeafYaml: React.FC> = ({child}) => { const [showData, setShowData] = React.useState(false) - const yml = child.type === 'yaml' ? child : undefined const {renderMap: {components}} = useContentLeafs() const parsedData = React.useMemo(() => { - if(!yml || ((yml.value?.trim() || '') === '')) return undefined + if(!child || ((child.value?.trim() || '') === '')) return undefined try { - return YAML.parse(yml.value, {prettyErrors: true}) + return YAML.parse(child.value, {prettyErrors: true}) } catch(e) { - console.error('yaml error', yml, e) + console.error('yaml error', child, e) return undefined } - }, [yml]) + }, [child]) - if(!yml) return null + if(!child) return null const Code = components.Code // eslint-disable-next-line deprecation/deprecation @@ -42,10 +41,10 @@ export const LeafYaml: React.FC = ({child}) => { {Code ? : -
{yml.value}
} +
{child.value}
}
diff --git a/packages/md-mui/LeafsMarkdown.ts b/packages/md-mui/LeafsMarkdown.ts index d291ecf..94370f7 100644 --- a/packages/md-mui/LeafsMarkdown.ts +++ b/packages/md-mui/LeafsMarkdown.ts @@ -15,11 +15,12 @@ import { LeafTable, LeafTableCell, LeafTableRow } from '@content-ui/md-mui/Leafs import { LeafYaml } from '@content-ui/md-mui/Leafs/LeafYaml' import { LeafTocListItem } from '@content-ui/md-mui/Leafs/LeafToc' import { LeafImage } from '@content-ui/md-mui/Leafs/LeafImage' -import { ContentRenderComponents, ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams } from '@content-ui/react/ContentLeafsContext' +import { ContentRenderComponents, ContentLeafsNodeMapping, LeafsRenderMapping, ContentLeafMatchParams, ContentLeafsPropsMapping } from '@content-ui/react/ContentLeafsContext' import { LeafDefList, LeafDefListDescription, LeafDefListTerm } from '@content-ui/md-mui/Leafs/LeafDefList' +import { CustomMdAstContent } from '@content-ui/struct/Ast' import { ComponentType } from 'react' -const leafs: ContentLeafsNodeMapping = { +const leafs: ContentLeafsNodeMapping> = { break: LeafBr, thematicBreak: LeafThematicBreak, text: LeafText, @@ -51,11 +52,8 @@ const leafs: ContentLeafsNodeMapping = { defList: LeafDefList, defListTerm: LeafDefListTerm, defListDescription: LeafDefListDescription, - // @ts-ignore definition: null, - // @ts-ignore imageReference: null, - // @ts-ignore linkReference: null, } @@ -70,7 +68,11 @@ export interface MuiContentRenderComponents extends ContentRenderComponents { Code?: ComponentType<{ value?: string, lang?: string }> } -export const renderMapping: LeafsRenderMapping = { +export const renderMapping: LeafsRenderMapping< + ContentLeafsNodeMapping>, + MuiContentRenderComponents, + ContentLeafMatchParams +> = { leafs: leafs, components: {}, matchLeaf: (p, l) => l[p.elem], diff --git a/packages/md-mui/Renderer.tsx b/packages/md-mui/Renderer.tsx index 4271235..b260418 100644 --- a/packages/md-mui/Renderer.tsx +++ b/packages/md-mui/Renderer.tsx @@ -1,9 +1,7 @@ -import { useContentSelection } from '@content-ui/react/ContentSelectionContext' import { Fragment, memo, ReactNode } from 'react' import { useContentContext } from '@content-ui/react/ContentFileContext' import Typography from '@mui/material/Typography' import { defaultTocIds, LeafToc, LeafTocContextType, useLeafToc } from '@content-ui/md-mui/Leafs/LeafToc' -import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' import { ContentLeaf } from '@content-ui/react/ContentLeaf' import { FootnoteSection } from '@content-ui/md-mui/Leafs/LeafFootnoteSection' @@ -13,14 +11,14 @@ export interface RendererProps { export const Renderer = ({handleTocClick}: RendererProps): ReactNode => { const {root} = useContentContext() - const editorSelection = useContentSelection() + // const editorSelection = useContentSelection() const {headlines, tocInject} = useLeafToc(root, defaultTocIds) const bodyNodes = root?.children?.filter(c => c.type !== 'footnoteDefinition') const footnoteDefinitions = root?.children?.filter(c => c.type === 'footnoteDefinition') - const startLine = editorSelection?.startLine - const endLine = editorSelection?.endLine + // const startLine = editorSelection?.startLine + // const endLine = editorSelection?.endLine const length = bodyNodes?.length || 0 return <> {length > 0 ? @@ -40,7 +38,7 @@ export const Renderer = ({handleTocClick}: RendererProps): ReactNode => { @@ -56,11 +54,12 @@ export const Renderer = ({handleTocClick}: RendererProps): ReactNode => { key={i} elem={child.type} child={child} - selected={isLeafSelected(child.position, startLine, endLine)} + // selected={isLeafSelected(child.position, startLine, endLine)} isFirst={i === 0} isLast={i === length - 1} />, )} + {/* todo: use components mapping to supply FootnoteSection */} {footnoteDefinitions?.length ? +export type ContentLeafInjected = 'decoIndex' | 'next' | 'renderMap'// keyof LeafsEngine export function ContentLeaf< TLeafDataMapping extends ContentLeafsPropsMapping, @@ -27,11 +27,11 @@ export function ContentLeaf< const Next = deco.next(0) as ReactBaseDecorator // todo: `Next` can not be typed in any way i've found, thus here no error will be shown, except for missing "injected props" + // maybe use `DecoratorPropsDefault/DecoratorPropsInjected`, but based on generics inference here? return } diff --git a/packages/react/ContentLeafsContext.tsx b/packages/react/ContentLeafsContext.tsx index 56a90a0..c288ac3 100644 --- a/packages/react/ContentLeafsContext.tsx +++ b/packages/react/ContentLeafsContext.tsx @@ -1,12 +1,10 @@ import { RootContent } from 'mdast' import React, { useMemo, memo, createContext, useContext } from 'react' import { useSettings } from '@content-ui/react/LeafSettings' -import { DecoratorPropsNext, ReactDeco } from '@content-ui/react/EngineDecorator' -import { ContentSelection } from '@content-ui/react/ContentSelectionContext' - -export type GenericLeafsDataSpec = { - [k: string]: D -} +import { DecoratorPropsNext, ReactBaseDecorator, ReactDeco } from '@content-ui/react/EngineDecorator' +import { ContentSelection, useContentSelection } from '@content-ui/react/ContentSelectionContext' +import { ContentLeafInjected } from '@content-ui/react/ContentLeaf' +import { isLeafSelected } from '@content-ui/react/Utils/isLeafSelected' export interface LeafsRenderMapping< TLeafsMapping extends {} = {}, @@ -24,7 +22,7 @@ export interface LeafsRenderMapping< /** * @experimental */ - TMatchResult = any, + TMatchResult = unknown, /** * @experimental */ @@ -35,30 +33,42 @@ export interface LeafsRenderMapping< /** * Responsible to match leafs of this mapping. */ - matchLeaf:

(params: P, leafs: TLeafsMapping) => TMatchResult + matchLeaf:

(params: P, leafs: TLeafsMapping) => TMatchResult & React.ComponentType

| undefined children?: never hooks?: THooks } +export type GenericLeafsDataSpec = { + [k: string]: D +} + /** - * A wider `React.ComponentType`, as the remapping had a lot of issues when `React.ComponentType` was used internally, somehow not reproducible here or in others with React18. - * But in ui-schema with the latest React 18 setup, `React.ComponentType` won't work without the `React.ComponentClass

` + * A wider `React.ComponentType`, as the remapping had a lot of issues when `React.ComponentType` was used internally. + * (this should be solved with the different `matchLeaf` typing approach, kept as note and for further investigation of the other issues) + * + * @todo it seems it has to do with incompatible props for different leafs + * - when using `React.ComponentType` the component types of each component in `leafs` must be strict + * - when using the loose `ReactLeafDefaultNodeType` the component types of `leafs` can differ, + * even allowing just `ContentLeafProps`, while using `.ComponentType` or `.FunctionComponent` requires e.g. `ContentLeafProps<'thematicBreak'>` + * - for FC it is related to their `propTypes` and `defaultProps` typing, not the actual `props` typing */ export type ReactLeafDefaultNodeType

= React.ComponentClass

| ((props: P, context?: any) => React.ReactNode) +// export type ReactLeafDefaultNodeType

= React.ComponentClass

| React.FunctionComponent

export type ReactLeafsNodeSpec = { - [K in keyof LDS]: ReactLeafDefaultNodeType>; + // [K in keyof LDS]?: ReactLeafDefaultNodeType> | null + // - partial to not require all implementations + // - null to support suppressing matching warning + [K in keyof LDS]?: React.ComponentType> | null } -export type ContentLeafMatchParams = { elem: string } - export interface LeafsEngine, TRender extends {}> { renderMap: TRender deco?: TDeco } -export interface ContentLeafPayload { - elem: string - selection?: ContentSelection +export interface ContentLeafPayload { + elem: TChild['type'] + child: TChild selected?: boolean // `true` when first Leaf inside the parent level isFirst?: boolean @@ -66,15 +76,8 @@ export interface ContentLeafPayload { isLast?: boolean } -type MdAstNodes = RootContent - -/** - * @todo make generic and easy to add further mdast types - */ -export type ContentLeafsPropsMapping = { - // [K in CustomMdAstContent['type']]: { elem: K, child: CustomMdAstContent } & ContentLeafPayload - // [K in Content['type']]: { elem: K, child: Content extends { type: K } ? Extract : never } & ContentLeafPayload - [K in MdAstNodes['type']]: { elem: K, child: Extract } & ContentLeafPayload +export type ContentLeafsPropsMapping = { + [K in TAstNodes['type']]: ContentLeafPayload> } export type ContentLeafsNodeMapping = ReactLeafsNodeSpec @@ -83,40 +86,72 @@ export type ContentRenderComponents = {} export type ContentLeafProps = ContentLeafsPropsMapping[S] -export type ContentRendererProps = { +export type ContentLeafMatchParams = { elem: string } + +/** + * @todo remove/split up elem+child and use some other typing for it + */ +export type ContentRendererProps< + TLeafDataMapping extends ContentLeafsPropsMapping = ContentLeafsPropsMapping, + TElem extends keyof ContentLeafsPropsMapping = keyof ContentLeafsPropsMapping +> = { renderMap: LeafsRenderMapping, ContentRenderComponents, ContentLeafMatchParams> - elem: string + elem: TElem + child: TLeafDataMapping[TElem]['child'] } export function ContentRenderer

( { renderMap, // eslint-disable-next-line @typescript-eslint/no-unused-vars - next, - ...p + next, decoIndex, + ...props }: P & ContentRendererProps, ): React.ReactElement

| null { const settings = useSettings() - const leafs = renderMap.leafs - const Leaf = renderMap.matchLeaf(p, leafs) + const Leaf = renderMap.matchLeaf(props, renderMap.leafs) + + if(typeof Leaf === 'undefined') { + console.error('No LeafNode found for ' + props.elem, props) + return null + } - if(!Leaf) { - console.error('No LeafNode found for ' + p.elem, p) + if(Leaf === null) { return null } return } export const ContentRendererMemo = memo(ContentRenderer) +export function ContentSelectionDecorator

( + { + renderMap, + next, decoIndex, + ...props + }: P & ContentRendererProps & { selection?: ContentSelection }, +): React.ReactElement

{ + const editorSelection = useContentSelection() + const Next = next(decoIndex) as ReactBaseDecorator + return +} + export const contentUIDecorators = new ReactDeco< DecoratorPropsNext & ContentRendererProps >() + .use(ContentSelectionDecorator) .use(ContentRendererMemo as typeof ContentRenderer) export const contentLeafsContext: React.Context< @@ -142,7 +177,7 @@ export function ContentLeafsProvider< TComponents extends ContentRenderComponents = ContentRenderComponents, TDeco extends ReactDeco<{}, {}, {}> = ReactDeco<{}, {}, {}>, TRender extends LeafsRenderMapping, TComponents, ContentLeafMatchParams> = LeafsRenderMapping, TComponents, ContentLeafMatchParams>, - // todo: integrate a typing which validates that the provided deco-result-props are compatible with props of `TRender2['leafs']` + // todo: integrate a typing which validates that the provided deco-result-props are compatible with props of `TRender['leafs']` >( { children, diff --git a/packages/struct/Ast.ts b/packages/struct/Ast.ts index e52251a..534dd5e 100644 --- a/packages/struct/Ast.ts +++ b/packages/struct/Ast.ts @@ -1,4 +1,4 @@ -import { Node, RootContent, Heading, Literal, Parent, PhrasingContent, Root } from 'mdast' +import { Node, RootContent, Heading, Literal, Parent, PhrasingContent, Root, List } from 'mdast' import { DefListNode, DefListDescriptionNode, DefListTermNode } from 'mdast-util-definition-list' export interface Underline extends Parent { @@ -11,6 +11,21 @@ export interface Insert extends Parent { children: PhrasingContent[] } +export interface Sub extends Parent { + type: 'sub' + children: PhrasingContent[] +} + +export interface Super extends Parent { + type: 'super' + children: PhrasingContent[] +} + +export interface Mark extends Parent { + type: 'mark' + children: PhrasingContent[] +} + export interface TocHNode { headline: Heading headlineIndex: number @@ -20,16 +35,33 @@ export interface TocHNode { nested?: TocHNode[] } +/** + * @todo is each TocListItem not also a TocList due to nesting? + * refactor together with the toc leafs + */ +export interface TocList extends Omit { + type: 'tocList' + children: TocListItem[] +} + export interface TocListItem extends Parent { type: 'tocListItem' - value: TocHNode + // todo: using `value` is incompatible with mdast itself, + // same for the `data` property, + // thus used a special property `headline`, + // no matter how, this means the Toc isn't interoperable + // with e.g. text/html transforms and the headline would vanish + headline: TocHNode } -export type CustomMdAstContent = RootContent | Underline | Insert | TocListItem | DefListNode | DefListTermNode | DefListDescriptionNode +export type CustomMdAstContent = + RootContent + | Underline | Insert + | Sub | Super + | Mark + | TocList | TocListItem + | DefListNode | DefListTermNode | DefListDescriptionNode + export type CustomMdAstNodes = CustomMdAstContent | Root export type MdAstGeneric = Node | Parent | Literal - -export interface WithMdAstChild { - child: C -} diff --git a/server/feed/src/handler/ReactHandler.tsx b/server/feed/src/handler/ReactHandler.tsx index c1d0a65..bdc968a 100644 --- a/server/feed/src/handler/ReactHandler.tsx +++ b/server/feed/src/handler/ReactHandler.tsx @@ -1,4 +1,5 @@ import { Viewer } from '@content-ui/md-mui/Viewer' +import { renderMapping } from '@content-ui/md-mui/LeafsMarkdown' import { ContentParser } from '@content-ui/md/parser/ContentParser' import { ContentFileProvider } from '@content-ui/react/ContentFileContext' import { ContentLeafsProvider, contentUIDecorators } from '@content-ui/react/ContentLeafsContext' @@ -6,7 +7,6 @@ import { Express } from 'express' import { renderToStaticMarkup } from 'react-dom/server' import { StaticRouter } from 'react-router-dom/server' import { VFile } from 'vfile' -import { contentUIMapping } from '../../../../apps/demo/src/components/ContentUI.js' export const reactHandler = (app: Express) => { app.get('/preview', async(req, res) => { @@ -29,7 +29,7 @@ This is **rendered static on server**. const html = renderToStaticMarkup( - + Date: Fri, 4 Oct 2024 22:44:40 +0200 Subject: [PATCH 5/6] reformat sandbox; add theme toggle sandbox; --- apps/sandbox/src/App.tsx | 87 +- .../src/components/CustomCodeMirror.tsx | 901 ++++++------- apps/sandbox/src/components/lezerMarkdown.ts | 1161 +++++++++-------- apps/sandbox/src/main.tsx | 54 +- apps/sandbox/src/pages/PageHome.tsx | 25 +- apps/sandbox/src/pages/PageInput.tsx | 4 +- apps/sandbox/src/theme.ts | 418 +++--- 7 files changed, 1280 insertions(+), 1370 deletions(-) diff --git a/apps/sandbox/src/App.tsx b/apps/sandbox/src/App.tsx index 73b9745..23b9c6d 100644 --- a/apps/sandbox/src/App.tsx +++ b/apps/sandbox/src/App.tsx @@ -1,44 +1,53 @@ -import { Route, Routes } from "react-router"; -import { PageHome } from "./pages/PageHome"; -import { MuiNavLink } from "@content-ui/md-mui/MuiComponents/MuiNavLink"; -import { PageInput } from "./pages/PageInput"; -import IcGitHub from "@mui/icons-material/GitHub"; +import IconButton from '@mui/material/IconButton' +import { Route, Routes } from 'react-router' +import { PageHome } from './pages/PageHome' +import { MuiNavLink } from '@content-ui/md-mui/MuiComponents/MuiNavLink' +import { PageInput } from './pages/PageInput' +import IcGitHub from '@mui/icons-material/GitHub' +import IcInvert from '@mui/icons-material/InvertColors' import Box from '@mui/material/Box' import Divider from '@mui/material/Divider' -export default function App() { - return ( - - - - home - - - input - void }) { + return ( + - - - - - - } /> - } /> - - - - ); + + + home + + + input + toggleTheme()} + sx={{display: 'flex', ml: 'auto'}} + > + + + + + + + + + }/> + }/> + + + + ) } diff --git a/apps/sandbox/src/components/CustomCodeMirror.tsx b/apps/sandbox/src/components/CustomCodeMirror.tsx index 21e3624..0770303 100644 --- a/apps/sandbox/src/components/CustomCodeMirror.tsx +++ b/apps/sandbox/src/components/CustomCodeMirror.tsx @@ -1,550 +1,437 @@ -import Box from "@mui/material/Box"; -import React from "react"; +import Box from '@mui/material/Box' +import React from 'react' import { - lineNumbers, - highlightActiveLineGutter, - highlightSpecialChars, - drawSelection, - dropCursor, - rectangularSelection, - highlightActiveLine, - keymap, - EditorView, - highlightWhitespace, - tooltips, - highlightTrailingWhitespace, - // crosshairCursor, -} from "@codemirror/view"; + lineNumbers, highlightActiveLineGutter, highlightSpecialChars, + drawSelection, dropCursor, + rectangularSelection, highlightActiveLine, keymap, + EditorView, highlightWhitespace, tooltips, highlightTrailingWhitespace, + // crosshairCursor, +} from '@codemirror/view' import { - foldGutter, - indentOnInput, - syntaxHighlighting, - defaultHighlightStyle, - bracketMatching, - foldKeymap, - StreamLanguage, - LanguageDescription, - LanguageSupport, - codeFolding, -} from "@codemirror/language"; -import { - history, - defaultKeymap, - historyKeymap, - indentWithTab, -} from "@codemirror/commands"; -import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; -import { - closeBrackets, - autocompletion, - closeBracketsKeymap, - completionKeymap, -} from "@codemirror/autocomplete"; -import { lintKeymap } from "@codemirror/lint"; -import { Compartment, EditorState, Extension } from "@codemirror/state"; -import { - CodeMirrorComponentProps, - CodeMirrorProps, -} from "@ui-schema/kit-codemirror/CodeMirror"; -import { - EditorThemeCustomStyles, - useEditorTheme, -} from "@ui-schema/material-code/useEditorTheme"; -import { useHighlightStyle } from "@ui-schema/material-code/useHighlightStyle"; -import { json } from "@codemirror/lang-json"; -import { javascript } from "@codemirror/lang-javascript"; -import { html } from "@codemirror/lang-html"; -import { css } from "@codemirror/lang-css"; -import { python } from "@codemirror/lang-python"; -import { wast } from "@codemirror/lang-wast"; -import { rust } from "@codemirror/lang-rust"; -import { xml } from "@codemirror/lang-xml"; -import { php } from "@codemirror/lang-php"; -import { - MariaSQL, - Cassandra, - MySQL, - PostgreSQL, - MSSQL, - StandardSQL, - sql, -} from "@codemirror/lang-sql"; -import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; -import { languages } from "@codemirror/language-data"; -import { lezer } from "@codemirror/lang-lezer"; -import { sCSS } from "@codemirror/legacy-modes/mode/css"; -import { shell } from "@codemirror/legacy-modes/mode/shell"; -import { csharp } from "@codemirror/legacy-modes/mode/clike"; -import { powerShell } from "@codemirror/legacy-modes/mode/powershell"; -import { http } from "@codemirror/legacy-modes/mode/http"; -import { yaml } from "@codemirror/legacy-modes/mode/yaml"; -import { - YAMLFrontMatter, - Footnote, - Mark, - Hashtag, - Mention, - Insert, -} from "./lezerMarkdown"; -import { - useCodeMirror, - useEditorClasses, - useExtension, -} from "@ui-schema/kit-codemirror"; -import { MuiCodeMirrorStyleProps } from "@ui-schema/material-code"; -import useTheme from "@mui/material/styles/useTheme"; + foldGutter, indentOnInput, syntaxHighlighting, + defaultHighlightStyle, bracketMatching, foldKeymap, + StreamLanguage, LanguageDescription, LanguageSupport, codeFolding, +} from '@codemirror/language' +import { history, defaultKeymap, historyKeymap, indentWithTab } from '@codemirror/commands' +import { highlightSelectionMatches, searchKeymap } from '@codemirror/search' +import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete' +import { lintKeymap } from '@codemirror/lint' +import { Compartment, EditorState, Extension } from '@codemirror/state' +import { CodeMirrorComponentProps, CodeMirrorProps } from '@ui-schema/kit-codemirror/CodeMirror' +import { EditorThemeCustomStyles, useEditorTheme } from '@ui-schema/material-code/useEditorTheme' +import { useHighlightStyle } from '@ui-schema/material-code/useHighlightStyle' +import { json } from '@codemirror/lang-json' +import { javascript } from '@codemirror/lang-javascript' +import { html } from '@codemirror/lang-html' +import { css } from '@codemirror/lang-css' +import { python } from '@codemirror/lang-python' +import { wast } from '@codemirror/lang-wast' +import { rust } from '@codemirror/lang-rust' +import { xml } from '@codemirror/lang-xml' +import { php } from '@codemirror/lang-php' +import { MariaSQL, Cassandra, MySQL, PostgreSQL, MSSQL, StandardSQL, sql } from '@codemirror/lang-sql' +import { markdown, markdownLanguage } from '@codemirror/lang-markdown' +import { languages } from '@codemirror/language-data' +import { lezer } from '@codemirror/lang-lezer' +import { sCSS } from '@codemirror/legacy-modes/mode/css' +import { shell } from '@codemirror/legacy-modes/mode/shell' +import { csharp } from '@codemirror/legacy-modes/mode/clike' +import { powerShell } from '@codemirror/legacy-modes/mode/powershell' +import { http } from '@codemirror/legacy-modes/mode/http' +import { yaml } from '@codemirror/legacy-modes/mode/yaml' +import { YAMLFrontMatter, Footnote, Mark, Hashtag, Mention, Insert } from './lezerMarkdown' +import { useCodeMirror, useEditorClasses, useExtension } from '@ui-schema/kit-codemirror' +import { MuiCodeMirrorStyleProps } from '@ui-schema/material-code' +import { useTheme } from '@mui/material/styles' const mdLang0 = markdown({ - base: markdownLanguage, - codeLanguages: [ - // adding yaml before `languages`, overwrites the included very-basic yaml language - LanguageDescription.of({ - name: "YAML", - alias: ["yaml", "yml"], - filename: /.(yaml|yml)$/i, - extensions: ["yaml", "yml"], - support: new LanguageSupport(StreamLanguage.define(yaml)), - }), - ], - extensions: [ - //GFM, Subscript, Superscript, - YAMLFrontMatter({ allBlocks: true }), - Footnote, - Mark, - Insert, - Hashtag, - Mention, - ], - /*extensions: [ + base: markdownLanguage, + codeLanguages: [ + // adding yaml before `languages`, overwrites the included very-basic yaml language + LanguageDescription.of({ + name: 'YAML', + alias: ['yaml', 'yml'], + filename: /.(yaml|yml)$/i, + extensions: ['yaml', 'yml'], + support: new LanguageSupport(StreamLanguage.define(yaml)), + }), + ], + extensions: [ + //GFM, Subscript, Superscript, + YAMLFrontMatter({allBlocks: true}), + Footnote, Mark, Insert, Hashtag, Mention, + ], + /*extensions: [ YAMLFrontMatter, Footnote, ],*/ -}); +}) const mdLang = markdown({ - base: markdownLanguage, - codeLanguages: [ - // adding yaml before `languages`, overwrites the included very-basic yaml language - LanguageDescription.of({ - name: "YAML", - alias: ["yaml", "yml"], - filename: /.(yaml|yml)$/i, - extensions: ["yaml", "yml"], - support: new LanguageSupport(StreamLanguage.define(yaml)), - }), - // todo: solve "markdown highlighting in codeblocks inside markdown", - // didn't seem possible without adding a further one, - // nested fence-codeblocks are impossible due to markdown, - // so that should be enough and the nested MD could even be "without nested code support" - // except for yaml/frontmatter - LanguageDescription.of({ - name: "Markdown", - alias: ["md", "markdown"], - extensions: ["md"], - support: mdLang0, - }), - ...languages, - ], - extensions: [ - //GFM, Subscript, Superscript, - YAMLFrontMatter({ allBlocks: true }), - Footnote, - Mark, - Insert, - Hashtag, - Mention, - ], - /*extensions: [ + base: markdownLanguage, + codeLanguages: [ + // adding yaml before `languages`, overwrites the included very-basic yaml language + LanguageDescription.of({ + name: 'YAML', + alias: ['yaml', 'yml'], + filename: /.(yaml|yml)$/i, + extensions: ['yaml', 'yml'], + support: new LanguageSupport(StreamLanguage.define(yaml)), + }), + // todo: solve "markdown highlighting in codeblocks inside markdown", + // didn't seem possible without adding a further one, + // nested fence-codeblocks are impossible due to markdown, + // so that should be enough and the nested MD could even be "without nested code support" + // except for yaml/frontmatter + LanguageDescription.of({ + name: 'Markdown', + alias: ['md', 'markdown'], + extensions: ['md'], + support: mdLang0, + }), + ...languages, + ], + extensions: [ + //GFM, Subscript, Superscript, + YAMLFrontMatter({allBlocks: true}), + Footnote, Mark, Insert, Hashtag, Mention, + ], + /*extensions: [ YAMLFrontMatter, Footnote, ],*/ -}); +}) -export const getHighlight = ( - lang: string | undefined, -): Extension | undefined => { - switch (lang?.toLowerCase()) { - case "json": - case "json5": - return json(); - case "js": - case "node": - case "nodejs": - case "javascript": - return javascript(); - case "jsx": - return javascript({ - jsx: true, - }); - case "ts": - case "typescript": - return javascript({ - typescript: true, - }); - case "tsx": - return javascript({ - jsx: true, - typescript: true, - }); - case "twig": - case "html": - return html(); - case "rss": - case "wsl": - case "xsd": - case "xml": - return xml(); - case "bash": - case "sh": - case "zsh": - case "ksh": - case "shell": - return StreamLanguage.define(shell); - case "powershell": - return StreamLanguage.define(powerShell); - case "c#": - case "cs": - case "csharp": - return StreamLanguage.define(csharp); - case "scss": - case "sass": - return StreamLanguage.define(sCSS); - case "http": - return StreamLanguage.define(http); - case "yml": - case "yaml": - return StreamLanguage.define(yaml); - case "css": - return css(); - case "python": - return python(); - case "wast": - return wast(); - case "rust": - return rust(); - case "injectablephp": - case "php": - return php(); - case "mysql": - return sql({ dialect: MySQL }); - case "ms sql": - case "mssql": - return sql({ dialect: MSSQL }); - case "mariasql": - return sql({ dialect: MariaSQL }); - case "postgresql": - return sql({ dialect: PostgreSQL }); - case "cassandra": - case "cql": - return sql({ dialect: Cassandra }); - case "bigquery": - case "standardsql": - return sql({ dialect: StandardSQL }); - case "sql": - return sql(); - case "md": - case "markdown": - // return markdown() - return mdLang; - case "lezer": - return lezer(); - default: - return undefined; - } -}; +export const getHighlight = (lang: string | undefined): Extension | undefined => { + switch(lang?.toLowerCase()) { + case 'json': + case 'json5': + return json() + case 'js': + case 'node': + case 'nodejs': + case 'javascript': + return javascript() + case 'jsx': + return javascript({ + jsx: true, + }) + case 'ts': + case 'typescript': + return javascript({ + typescript: true, + }) + case 'tsx': + return javascript({ + jsx: true, + typescript: true, + }) + case 'twig': + case 'html': + return html() + case 'rss': + case 'wsl': + case 'xsd': + case 'xml': + return xml() + case 'bash': + case 'sh': + case 'zsh': + case 'ksh': + case 'shell': + return StreamLanguage.define(shell) + case 'powershell': + return StreamLanguage.define(powerShell) + case 'c#': + case 'cs': + case 'csharp': + return StreamLanguage.define(csharp) + case 'scss': + case 'sass': + return StreamLanguage.define(sCSS) + case 'http': + return StreamLanguage.define(http) + case 'yml': + case 'yaml': + return StreamLanguage.define(yaml) + case 'css': + return css() + case 'python': + return python() + case 'wast': + return wast() + case 'rust': + return rust() + case 'injectablephp': + case 'php': + return php() + case 'mysql': + return sql({dialect: MySQL}) + case 'ms sql': + case 'mssql': + return sql({dialect: MSSQL}) + case 'mariasql': + return sql({dialect: MariaSQL}) + case 'postgresql': + return sql({dialect: PostgreSQL}) + case 'cassandra': + case 'cql': + return sql({dialect: Cassandra}) + case 'bigquery': + case 'standardsql': + return sql({dialect: StandardSQL}) + case 'sql': + return sql() + case 'md': + case 'markdown': + // return markdown() + return mdLang + case 'lezer': + return lezer() + default: + return undefined + } +} -export type CustomCodeMirrorProps = CodeMirrorComponentProps & - MuiCodeMirrorStyleProps & { - dense?: boolean; - onViewLifecycle?: CodeMirrorProps["onViewLifecycle"]; - style?: React.CSSProperties; +export type CustomCodeMirrorProps = CodeMirrorComponentProps & MuiCodeMirrorStyleProps & { + dense?: boolean + onViewLifecycle?: CodeMirrorProps['onViewLifecycle'] + style?: React.CSSProperties // containerRef?: React.MutableRefObject - lang?: string; - extraMods?: boolean; + lang?: string + extraMods?: boolean /** * @todo implement a user notification, if the first tab in the session, remind to use ESC+TAB * needs sessionStorage/localStorage */ - enableTabIndent?: boolean; - highlightWhitespaces?: boolean; - autoFocus?: boolean | number; - paddingBottom?: boolean | number | string; - id?: string; + enableTabIndent?: boolean + highlightWhitespaces?: boolean + autoFocus?: boolean | number + paddingBottom?: boolean | number | string + id?: string /** * @experimental */ - enableSpellCheck?: boolean; - }; + enableSpellCheck?: boolean +} + -export const CustomCodeMirror: React.FC = ({ - // values we want to override in this component - value, - extensions, - lang, - dense, - variant: customVariant, - classNamesContent, - onChange, - onViewLifecycle, - effects, - style, - highlightWhitespaces, - autoFocus, - extraMods = true, - enableTabIndent = true, - paddingBottom = false, - enableSpellCheck = false, - id, -}) => { - const containerRef = React.useRef(null); - // containerRef.current ||= containerRefProps?.current || null - // refs for extensions need to be created before the extension - const editorAttributesCompartment = React.useRef( - new Compartment(), - ); - const contentAttributesCompartment = React.useRef( - new Compartment(), - ); - const keymapCompartment = React.useRef(new Compartment()); - const highlightCompartment = React.useRef(new Compartment()); - const highlightWhitespaceCompartment = React.useRef( - new Compartment(), - ); - const highlightTrailingWhitespaceCompartment = React.useRef( - new Compartment(), - ); - // todo: that `objet.values` here doesn't get changes when value keeps the same but property name has changed - // eslint-disable-next-line react-hooks/exhaustive-deps - const editorStyle = React.useMemo( - () => style || {}, - [Object.values(style || {})], - ); - const { palette } = useTheme(); +export const CustomCodeMirror: React.FC = ( + { + // values we want to override in this component + value, extensions, lang, + dense, variant: customVariant, + classNamesContent, onChange, + onViewLifecycle, effects, + style, + highlightWhitespaces, + autoFocus, + extraMods = true, + enableTabIndent = true, + paddingBottom = false, + enableSpellCheck = false, + id, + }, +) => { + const containerRef = React.useRef(null) + // containerRef.current ||= containerRefProps?.current || null + // refs for extensions need to be created before the extension + const editorAttributesCompartment = React.useRef(new Compartment()) + const contentAttributesCompartment = React.useRef(new Compartment()) + const keymapCompartment = React.useRef(new Compartment()) + const highlightCompartment = React.useRef(new Compartment()) + const highlightWhitespaceCompartment = React.useRef(new Compartment()) + const highlightTrailingWhitespaceCompartment = React.useRef(new Compartment()) + // todo: that `objet.values` here doesn't get changes when value keeps the same but property name has changed + // eslint-disable-next-line react-hooks/exhaustive-deps + const editorStyle = React.useMemo(() => style || {}, [Object.values(style || {})]) + const {palette} = useTheme() - const customStyles = React.useMemo>( - () => ({ - activeSelection: - palette.mode === "dark" ? "#182f2f" : "rgba(210,243,239,0.76)", - activeLine: - palette.mode === "dark" - ? "rgba(5, 115, 115, 0.11)" - : "rgba(216,234,231,0.34)", - paddingBottom: paddingBottom, - // activeSelection: '#ffffff', - // activeLine: '#ffffff', - // textColor: '#ffffff', - }), - [palette.mode, paddingBottom], - ); - const theme = useEditorTheme( - typeof onChange === "undefined", - dense, - customVariant, - customStyles as EditorThemeCustomStyles, - ); + const customStyles = React.useMemo>(() => ({ + activeSelection: palette.mode === 'dark' ? '#182f2f' : 'rgba(210,243,239,0.76)', + activeLine: palette.mode === 'dark' ? 'rgba(5, 115, 115, 0.11)' : 'rgba(216,234,231,0.34)', + paddingBottom: paddingBottom, + // activeSelection: '#ffffff', + // activeLine: '#ffffff', + // textColor: '#ffffff', + }), [palette.mode, paddingBottom]) + const theme = useEditorTheme(typeof onChange === 'undefined', dense, customVariant, customStyles as EditorThemeCustomStyles) - const highlightStyle = useHighlightStyle({headlineUnderline: false}); - const { init: initHighlightExt, effects: effectsHighlightExt } = useExtension( - () => - syntaxHighlighting(highlightStyle || defaultHighlightStyle, { - fallback: true, - }), - [highlightStyle], - ); - const { init: initThemeExt, effects: effectsThemeExt } = useExtension( - () => theme, - [theme], - ); - const effectsRef = React.useRef<((editor: EditorView) => void)[]>( - effects || [], - ); + const highlightStyle = useHighlightStyle({headlineUnderline: false}) + const {init: initHighlightExt, effects: effectsHighlightExt} = useExtension( + () => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}), + [highlightStyle], + ) + const {init: initThemeExt, effects: effectsThemeExt} = useExtension( + () => theme, + [theme], + ) + const effectsRef = React.useRef<((editor: EditorView) => void)[]>(effects || []) - const extensionsAll = React.useMemo( - () => [ - editorAttributesCompartment.current.of( - EditorView.editorAttributes.of({}), - ), - contentAttributesCompartment.current.of( - EditorView.contentAttributes.of({}), - ), - lineNumbers(), - EditorView.lineWrapping, - highlightActiveLineGutter(), - highlightSpecialChars(), - history(), - foldGutter(), - drawSelection(), - dropCursor(), - EditorState.allowMultipleSelections.of(true), - indentOnInput(), - bracketMatching(), - closeBrackets(), - autocompletion(), - rectangularSelection(), - highlightActiveLine(), - highlightSelectionMatches(), - keymapCompartment.current.of([]), - highlightCompartment.current.of([]), - highlightWhitespaceCompartment.current.of([]), - highlightTrailingWhitespaceCompartment.current.of([]), - ...(extraMods - ? [ + const extensionsAll = React.useMemo(() => [ + editorAttributesCompartment.current.of(EditorView.editorAttributes.of({})), + contentAttributesCompartment.current.of(EditorView.contentAttributes.of({})), + lineNumbers(), + EditorView.lineWrapping, + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + highlightActiveLine(), + highlightSelectionMatches(), + keymapCompartment.current.of([]), + highlightCompartment.current.of([]), + highlightWhitespaceCompartment.current.of([]), + highlightTrailingWhitespaceCompartment.current.of([]), + ...extraMods ? [ codeFolding(), // hoverTooltip(), tooltips(), - ] - : []), - // indentUnit.of(' '), - // todo: csv with tab requires keeping them as is - new Compartment().of(EditorState.tabSize.of(4)), - initHighlightExt(), - initThemeExt(), - ...(extensions || []), - // todo: if any unmounting of these extensions is concurrently with an remote change, it may not be registered - // refactor the whole extensions to a combined part, which is reconciled "manually" in sync and on effects - ], - [extraMods, initHighlightExt, initThemeExt, extensions], - ); + ] : [], + // indentUnit.of(' '), + // todo: csv with tab requires keeping them as is + new Compartment().of(EditorState.tabSize.of(4)), + initHighlightExt(), + initThemeExt(), + ...(extensions || []), + // todo: if any unmounting of these extensions is concurrently with an remote change, it may not be registered + // refactor the whole extensions to a combined part, which is reconciled "manually" in sync and on effects + ], [extraMods, initHighlightExt, initThemeExt, extensions]) - // attach parent plugin effects first - React.useMemo(() => { - if (!effects) return effectsRef.current; - effectsRef.current.push(...effects); - }, [effects]); + // attach parent plugin effects first + React.useMemo(() => { + if(!effects) return effectsRef.current + effectsRef.current.push(...effects) + }, [effects]) - // attach each plugin effect separately (thus only the one which changes get reconfigured) - React.useMemo(() => { - if (!effectsHighlightExt) return; - effectsRef.current.push(...effectsHighlightExt); - }, [effectsHighlightExt]); - React.useMemo(() => { - if (!effectsThemeExt) return; - effectsRef.current.push(...effectsThemeExt); - }, [effectsThemeExt]); + // attach each plugin effect separately (thus only the one which changes get reconfigured) + React.useMemo(() => { + if(!effectsHighlightExt) return + effectsRef.current.push(...effectsHighlightExt) + }, [effectsHighlightExt]) + React.useMemo(() => { + if(!effectsThemeExt) return + effectsRef.current.push(...effectsThemeExt) + }, [effectsThemeExt]) - const editor = useCodeMirror( - onChange, - value, - extensionsAll, - effectsRef.current.splice(0, effectsRef.current.length), - containerRef, - undefined, - onViewLifecycle, - ); + const editor = useCodeMirror( + onChange, + value, + extensionsAll, + effectsRef.current.splice(0, effectsRef.current.length), + containerRef, + undefined, + onViewLifecycle, + ) - // but extensions need to receive both: Compartment and Editor (and optionally their values) - // to be able to dispatch the correct effects - useEditorClasses( - editorAttributesCompartment.current, - editor, - classNamesContent, - ); + // but extensions need to receive both: Compartment and Editor (and optionally their values) + // to be able to dispatch the correct effects + useEditorClasses(editorAttributesCompartment.current, editor, classNamesContent) - React.useEffect(() => { - if (!editor || !enableSpellCheck) return; - // todo: spell check is working in e.g. Chrome but uses the device lang instead of app/component - const contentAttributesCompartmentTmp = - contentAttributesCompartment.current; - editor.dispatch({ - // https://github.com/codemirror/dev/issues/1020 - effects: contentAttributesCompartmentTmp.reconfigure( - enableSpellCheck - ? EditorView.contentAttributes.of({ - autocorrect: "on", - autocapitalize: "on", - spellcheck: "true", - }) - : EditorView.contentAttributes.of({}), - ), - }); - return () => - editor.dispatch({ - // https://github.com/codemirror/dev/issues/1020 - // ...enableSpellCheck ? - // [EditorView.contentAttributes.of({autocorrect: 'on', autocapitalize: 'on', spellcheck: 'true'})] : [], - effects: contentAttributesCompartmentTmp.reconfigure( - EditorView.contentAttributes.of({}), - ), - }); - }, [editor, enableSpellCheck]); + React.useEffect(() => { + if(!editor || !enableSpellCheck) return + // todo: spell check is working in e.g. Chrome but uses the device lang instead of app/component + const contentAttributesCompartmentTmp = contentAttributesCompartment.current + editor.dispatch({ + // https://github.com/codemirror/dev/issues/1020 + effects: contentAttributesCompartmentTmp + .reconfigure( + enableSpellCheck ? + EditorView.contentAttributes.of({autocorrect: 'on', autocapitalize: 'on', spellcheck: 'true'}) : + EditorView.contentAttributes.of({}), + ), + }) + return () => editor.dispatch({ + // https://github.com/codemirror/dev/issues/1020 + // ...enableSpellCheck ? + // [EditorView.contentAttributes.of({autocorrect: 'on', autocapitalize: 'on', spellcheck: 'true'})] : [], + effects: contentAttributesCompartmentTmp + .reconfigure(EditorView.contentAttributes.of({})), + }) + }, [editor, enableSpellCheck]) + + React.useEffect(() => { + if(!editor) return + editor.dispatch({ + effects: highlightWhitespaceCompartment.current + .reconfigure(onChange && highlightWhitespaces ? highlightWhitespace() : []), + }) + }, [editor, highlightWhitespaceCompartment, highlightWhitespaces, onChange]) + + React.useEffect(() => { + if(!editor) return - React.useEffect(() => { - if (!editor) return; - editor.dispatch({ - effects: highlightWhitespaceCompartment.current.reconfigure( - onChange && highlightWhitespaces ? highlightWhitespace() : [], - ), - }); - }, [editor, highlightWhitespaceCompartment, highlightWhitespaces, onChange]); + const highlightExt = lang && getHighlight(lang) + editor.dispatch({ + effects: highlightCompartment.current + .reconfigure(highlightExt ? [highlightExt] : []), + }) + }, [editor, highlightCompartment, lang]) - React.useEffect(() => { - if (!editor) return; + React.useEffect(() => { + if(!editor) return - const highlightExt = lang && getHighlight(lang); - editor.dispatch({ - effects: highlightCompartment.current.reconfigure( - highlightExt ? [highlightExt] : [], - ), - }); - }, [editor, highlightCompartment, lang]); + editor.dispatch({ + effects: keymapCompartment.current + .reconfigure([ + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap.filter(k => k.key !== 'Mod-Enter'), + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + ...enableTabIndent ? [indentWithTab] : [], + ]), + ]), + }) + }, [editor, keymapCompartment, enableTabIndent]) - React.useEffect(() => { - if (!editor) return; - editor.dispatch({ - effects: keymapCompartment.current.reconfigure([ - keymap.of([ - ...closeBracketsKeymap, - ...defaultKeymap.filter((k) => k.key !== "Mod-Enter"), - ...searchKeymap, - ...historyKeymap, - ...foldKeymap, - ...completionKeymap, - ...lintKeymap, - ...(enableTabIndent ? [indentWithTab] : []), - ]), - ]), - }); - }, [editor, keymapCompartment, enableTabIndent]); + React.useEffect(() => { + if(!editor) return + editor.dispatch({ + effects: highlightTrailingWhitespaceCompartment.current + .reconfigure(onChange && extraMods ? highlightTrailingWhitespace() : []), + }) + }, [editor, highlightTrailingWhitespaceCompartment, onChange, extraMods]) - React.useEffect(() => { - if (!editor) return; - editor.dispatch({ - effects: highlightTrailingWhitespaceCompartment.current.reconfigure( - onChange && extraMods ? highlightTrailingWhitespace() : [], - ), - }); - }, [editor, highlightTrailingWhitespaceCompartment, onChange, extraMods]); - React.useEffect(() => { - if (!editor || !autoFocus) return; - const autoFocusDelay = typeof autoFocus === "number" ? autoFocus : 75; - const setFocus = () => { - editor.focus(); - if (editor.hasFocus) { - const length = editor.state.doc.length; - editor.dispatch({ selection: { anchor: length, head: length } }); - window.clearInterval(timer); - } - }; - const timer = window.setInterval(setFocus, autoFocusDelay); - return () => window.clearInterval(timer); - }, [editor, autoFocus]); + React.useEffect(() => { + if(!editor || !autoFocus) return + const autoFocusDelay = typeof autoFocus === 'number' ? autoFocus : 75 + const setFocus = () => { + editor.focus() + if(editor.hasFocus) { + const length = editor.state.doc.length + editor.dispatch({selection: {anchor: length, head: length}}) + window.clearInterval(timer) + } + } + const timer = window.setInterval(setFocus, autoFocusDelay) + return () => window.clearInterval(timer) + }, [editor, autoFocus]) - // todo: optimize font size config at theme ext hook - // return

- return ( - + return - ); -}; +} diff --git a/apps/sandbox/src/components/lezerMarkdown.ts b/apps/sandbox/src/components/lezerMarkdown.ts index 8dc43b1..1b2fdd2 100644 --- a/apps/sandbox/src/components/lezerMarkdown.ts +++ b/apps/sandbox/src/components/lezerMarkdown.ts @@ -2,471 +2,472 @@ * Copied and adjusted, https://github.com/erykwalder/lezer-markdown-obsidian/blob/6223674c535addf0fa60c8a04e3ebf5fd8aa3d7e/src/extensions.ts * @licence MIT 2023 Eric Rykwalder, see https://github.com/erykwalder/lezer-markdown-obsidian/blob/6223674c535addf0fa60c8a04e3ebf5fd8aa3d7e/LICENSE */ -import { foldNodeProp, StreamLanguage } from "@codemirror/language"; -import { yaml } from "@codemirror/legacy-modes/mode/yaml"; -import { Input, parseMixed } from "@lezer/common"; +import { foldNodeProp, StreamLanguage } from '@codemirror/language' +import { yaml } from '@codemirror/legacy-modes/mode/yaml' +import { Input, parseMixed } from '@lezer/common' import { - BlockContext, - Element, - InlineContext, - LeafBlock, - LeafBlockParser, - Line, - MarkdownConfig, - DelimiterType, -} from "@lezer/markdown"; -import { styleTags, tags } from "@lezer/highlight"; - -declare module "@lezer/markdown" { - interface BlockContext { - readonly input: Input; - checkedYaml: boolean | null; - } + BlockContext, + Element, + InlineContext, + LeafBlock, + LeafBlockParser, + Line, + MarkdownConfig, + DelimiterType, +} from '@lezer/markdown' +import { styleTags, tags } from '@lezer/highlight' + +declare module '@lezer/markdown' { + interface BlockContext { + readonly input: Input; + checkedYaml: boolean | null; + } } interface BlockContextWithInput extends BlockContext { - readonly input: Input; + readonly input: Input; } -const CommentDelim = { resolve: "Comment", mark: "CommentMarker" }; +const CommentDelim = {resolve: 'Comment', mark: 'CommentMarker'} export const Comment: MarkdownConfig = { - defineNodes: ["Comment", "CommentMarker"], - parseBlock: [ - { - name: "CommentBlock", - endLeaf: (_, line: Line) => { - return line.text.slice(line.pos, line.pos + 2) == "%%"; - }, - parse(cx: BlockContext, line: Line) { - if (line.text.slice(line.pos, line.pos + 2) != "%%") { - return false; - } - const start = cx.lineStart + line.pos; - const markers = [cx.elt("CommentMarker", start, start + 2)]; - const regex = /(^|[^\\])%%/; - let remaining = line.text.slice(line.pos + 2); - let startOffset = 2; - let match; - while (!(match = regex.exec(remaining)) && cx.nextLine()) { - remaining = line.text; - startOffset = 0; - } - let end; - if (match) { - const lineEnd = match.index + match[0].length + startOffset; - end = cx.lineStart + lineEnd; - markers.push(cx.elt("CommentMarker", end - 2, end)); - if ( - lineEnd == line.text.length || - /^\s+$/.test(line.text.slice(lineEnd)) - ) { - cx.nextLine(); - } else { - line.pos = line.skipSpace(lineEnd); - } - } else { - end = cx.lineStart + line.text.length; - } - cx.addElement(cx.elt("Comment", start, end, markers)); - return true; - }, - }, - ], - parseInline: [ - { - name: "CommentInline", - parse(cx: InlineContext, next: number, pos: number) { - if (next == 37 && cx.char(pos + 1) == 37) { - let canClose = true; - if ( - cx.slice(cx.offset, pos).lastIndexOf("\n") > - cx.slice(cx.offset, pos).lastIndexOf("%%") - ) { - canClose = false; - } - return cx.addDelimiter(CommentDelim, pos, pos + 2, true, canClose); - } - return -1; - }, - }, - ], -}; + defineNodes: ['Comment', 'CommentMarker'], + parseBlock: [ + { + name: 'CommentBlock', + endLeaf: (_, line: Line) => { + return line.text.slice(line.pos, line.pos + 2) == '%%' + }, + parse(cx: BlockContext, line: Line) { + if(line.text.slice(line.pos, line.pos + 2) != '%%') { + return false + } + const start = cx.lineStart + line.pos + const markers = [cx.elt('CommentMarker', start, start + 2)] + const regex = /(^|[^\\])%%/ + let remaining = line.text.slice(line.pos + 2) + let startOffset = 2 + let match + while(!(match = regex.exec(remaining)) && cx.nextLine()) { + remaining = line.text + startOffset = 0 + } + let end + if(match) { + const lineEnd = match.index + match[0].length + startOffset + end = cx.lineStart + lineEnd + markers.push(cx.elt('CommentMarker', end - 2, end)) + if( + lineEnd == line.text.length || + /^\s+$/.test(line.text.slice(lineEnd)) + ) { + cx.nextLine() + } else { + line.pos = line.skipSpace(lineEnd) + } + } else { + end = cx.lineStart + line.text.length + } + cx.addElement(cx.elt('Comment', start, end, markers)) + return true + }, + }, + ], + parseInline: [ + { + name: 'CommentInline', + parse(cx: InlineContext, next: number, pos: number) { + if(next == 37 && cx.char(pos + 1) == 37) { + let canClose = true + if( + cx.slice(cx.offset, pos).lastIndexOf('\n') > + cx.slice(cx.offset, pos).lastIndexOf('%%') + ) { + canClose = false + } + return cx.addDelimiter(CommentDelim, pos, pos + 2, true, canClose) + } + return -1 + }, + }, + ], +} class FootnoteReferenceParser implements LeafBlockParser { - constructor(private labelEnd: number) {} + constructor(private labelEnd: number) { + } + + nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) { + if(isFootnoteRef(line.text) != -1) { + return this.complete(cx, leaf) + } + return false + } - nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) { - if (isFootnoteRef(line.text) != -1) { - return this.complete(cx, leaf); + finish(cx: BlockContext, leaf: LeafBlock) { + return this.complete(cx, leaf) + } + + complete(cx: BlockContext, leaf: LeafBlock) { + cx.addLeafElement( + leaf, + cx.elt( + 'FootnoteReference', + leaf.start, + leaf.start + leaf.content.length, + [ + cx.elt('FootnoteMark', leaf.start, leaf.start + 2), + cx.elt('FootnoteLabel', leaf.start + 2, this.labelEnd - 2), + cx.elt('FootnoteMark', this.labelEnd - 2, this.labelEnd), + ...cx.parser.parseInline( + leaf.content.slice(this.labelEnd - leaf.start), + this.labelEnd, + ), + ], + ), + ) + return true } - return false; - } - - finish(cx: BlockContext, leaf: LeafBlock) { - return this.complete(cx, leaf); - } - - complete(cx: BlockContext, leaf: LeafBlock) { - cx.addLeafElement( - leaf, - cx.elt( - "FootnoteReference", - leaf.start, - leaf.start + leaf.content.length, - [ - cx.elt("FootnoteMark", leaf.start, leaf.start + 2), - cx.elt("FootnoteLabel", leaf.start + 2, this.labelEnd - 2), - cx.elt("FootnoteMark", this.labelEnd - 2, this.labelEnd), - ...cx.parser.parseInline( - leaf.content.slice(this.labelEnd - leaf.start), - this.labelEnd, - ), - ], - ), - ); - return true; - } } export const Footnote: MarkdownConfig = { - defineNodes: [ - "Footnote", - "FootnoteLabel", - "FootnoteMark", - "FootnoteReference", - ], - parseInline: [ - { - name: "Footnote", - parse(cx: InlineContext, _, pos: number) { - // typically [^1], but inside can match any characters but - // square brackets and spaces. - const match = /^\[\^[^\s[\]]+\]/.exec(cx.text.slice(pos - cx.offset)); - if (match) { - const end = pos + match[0].length; - return cx.addElement( - cx.elt("Footnote", pos, end, [ - cx.elt("FootnoteMark", pos, pos + 2), - cx.elt("FootnoteLabel", pos + 2, end - 1), - cx.elt("FootnoteMark", end - 1, end), - ]), - ); - } - return -1; - }, - before: "Link", - }, - ], - parseBlock: [ - { - name: "FootnoteReference", - leaf(_, leaf: LeafBlock): LeafBlockParser | null { - const ref = isFootnoteRef(leaf.content); - if (ref != -1) { - return new FootnoteReferenceParser(leaf.start + ref); - } - return null; - }, - before: "LinkReference", - }, - ], - props: [ - styleTags({ - FootnoteReference: [tags.content, tags.emphasis], - FootnoteMark: tags.processingInstruction, - FootnoteLabel: [tags.link, tags.url], - }), - ], -}; + defineNodes: [ + 'Footnote', + 'FootnoteLabel', + 'FootnoteMark', + 'FootnoteReference', + ], + parseInline: [ + { + name: 'Footnote', + parse(cx: InlineContext, _, pos: number) { + // typically [^1], but inside can match any characters but + // square brackets and spaces. + const match = /^\[\^[^\s[\]]+\]/.exec(cx.text.slice(pos - cx.offset)) + if(match) { + const end = pos + match[0].length + return cx.addElement( + cx.elt('Footnote', pos, end, [ + cx.elt('FootnoteMark', pos, pos + 2), + cx.elt('FootnoteLabel', pos + 2, end - 1), + cx.elt('FootnoteMark', end - 1, end), + ]), + ) + } + return -1 + }, + before: 'Link', + }, + ], + parseBlock: [ + { + name: 'FootnoteReference', + leaf(_, leaf: LeafBlock): LeafBlockParser | null { + const ref = isFootnoteRef(leaf.content) + if(ref != -1) { + return new FootnoteReferenceParser(leaf.start + ref) + } + return null + }, + before: 'LinkReference', + }, + ], + props: [ + styleTags({ + FootnoteReference: [tags.content, tags.emphasis], + FootnoteMark: tags.processingInstruction, + FootnoteLabel: [tags.link, tags.url], + }), + ], +} function isFootnoteRef(content: string): number { - const match = /^\[\^[^\s[\]]+\]:/.exec(content); - return match ? match[0].length : -1; + const match = /^\[\^[^\s[\]]+\]:/.exec(content) + return match ? match[0].length : -1 } const hashtagRE = - /^[^\u2000-\u206F\u2E00-\u2E7F'!"#$%&()*+,.:;<=>?@^`{|}~[\]\\\s]+/; + /^[^\u2000-\u206F\u2E00-\u2E7F'!"#$%&()*+,.:;<=>?@^`{|}~[\]\\\s]+/ export const Hashtag: MarkdownConfig = { - defineNodes: ["Hashtag", "HashtagMark", "HashtagLabel"], - parseInline: [ - { - name: "Hashtag", - parse(cx: InlineContext, next: number, pos: number) { - if (next != 35 /* # */) { - return -1; - } - const start = pos; - pos += 1; - const match = hashtagRE.exec(cx.text.slice(pos - cx.offset)); - if (match && /[\D|\d]/.test(match[0])) { - pos += match[0].length; - return cx.addElement( - cx.elt("Hashtag", start, pos, [ - cx.elt("HashtagMark", start, start + 1), - cx.elt("HashtagLabel", start + 1, pos), - ]), - ); - } - return -1; - }, - }, - ], - props: [ - styleTags({ - Hashtag: tags.url, - HashtagMark: tags.processingInstruction, - HashtagLabel: [tags.link, tags.url], - }), - ], -}; + defineNodes: ['Hashtag', 'HashtagMark', 'HashtagLabel'], + parseInline: [ + { + name: 'Hashtag', + parse(cx: InlineContext, next: number, pos: number) { + if(next != 35 /* # */) { + return -1 + } + const start = pos + pos += 1 + const match = hashtagRE.exec(cx.text.slice(pos - cx.offset)) + if(match && /[\D|\d]/.test(match[0])) { + pos += match[0].length + return cx.addElement( + cx.elt('Hashtag', start, pos, [ + cx.elt('HashtagMark', start, start + 1), + cx.elt('HashtagLabel', start + 1, pos), + ]), + ) + } + return -1 + }, + }, + ], + props: [ + styleTags({ + Hashtag: tags.url, + HashtagMark: tags.processingInstruction, + HashtagLabel: [tags.link, tags.url], + }), + ], +} // removed `dot` here to allow that for combined names const mentionRE = - /^[^\u2000-\u206E\u2E00-\u2E7F'!"#$%&()*+,:;<=>?^`{|}~[\]\\\s]+/; + /^[^\u2000-\u206E\u2E00-\u2E7F'!"#$%&()*+,:;<=>?^`{|}~[\]\\\s]+/ export const Mention: MarkdownConfig = { - defineNodes: ["Mention", "MentionMark", "MentionLabel"], - parseInline: [ - { - name: "Mention", - parse(cx: InlineContext, next: number, pos: number) { - if (next != 64 /* @ */) { - return -1; - } - const start = pos; - pos += 1; - const match = mentionRE.exec(cx.text.slice(pos - cx.offset)); - if (match && /[\D|\d]/.test(match[0])) { - pos += match[0].length; - if (match[0][match[0].length - 1] === ".") { - // if the last is a dot, exclude dot again, e.g. sentence endings - pos -= 1; - } - return cx.addElement( - cx.elt("Mention", start, pos, [ - cx.elt("MentionMark", start, start + 1), - cx.elt("MentionLabel", start + 1, pos), - ]), - ); - } - return -1; - }, - }, - ], - props: [ - styleTags({ - Mention: tags.url, - MentionMark: tags.processingInstruction, - MentionLabel: [tags.link, tags.url], - }), - ], -}; + defineNodes: ['Mention', 'MentionMark', 'MentionLabel'], + parseInline: [ + { + name: 'Mention', + parse(cx: InlineContext, next: number, pos: number) { + if(next != 64 /* @ */) { + return -1 + } + const start = pos + pos += 1 + const match = mentionRE.exec(cx.text.slice(pos - cx.offset)) + if(match && /[\D|\d]/.test(match[0])) { + pos += match[0].length + if(match[0][match[0].length - 1] === '.') { + // if the last is a dot, exclude dot again, e.g. sentence endings + pos -= 1 + } + return cx.addElement( + cx.elt('Mention', start, pos, [ + cx.elt('MentionMark', start, start + 1), + cx.elt('MentionLabel', start + 1, pos), + ]), + ) + } + return -1 + }, + }, + ], + props: [ + styleTags({ + Mention: tags.url, + MentionMark: tags.processingInstruction, + MentionLabel: [tags.link, tags.url], + }), + ], +} export const InternalLink: MarkdownConfig = { - defineNodes: [ - "Embed", - "EmbedMark", - "InternalLink", - "InternalMark", - "InternalPath", - "InternalSubpath", - "InternalDisplay", - ], - parseInline: [ - { - name: "InternalLink", - parse(cx: InlineContext, _, pos: number) { - const el = parseInternalLink(cx, pos); - if (el) { - return cx.addElement(el); - } - return -1; - }, - before: "Link", - }, - { - name: "Embed", - parse(cx: InlineContext, next: number, pos: number): number { - if (next != 33) { - return -1; - } - const link = parseInternalLink(cx, pos + 1); - if (link) { - const embedMark = cx.elt("EmbedMark", pos, pos + 1); - return cx.addElement( - cx.elt("Embed", pos, link.to, [embedMark, link]), - ); - } - return -1; - }, - before: "Image", - }, - ], -}; + defineNodes: [ + 'Embed', + 'EmbedMark', + 'InternalLink', + 'InternalMark', + 'InternalPath', + 'InternalSubpath', + 'InternalDisplay', + ], + parseInline: [ + { + name: 'InternalLink', + parse(cx: InlineContext, _, pos: number) { + const el = parseInternalLink(cx, pos) + if(el) { + return cx.addElement(el) + } + return -1 + }, + before: 'Link', + }, + { + name: 'Embed', + parse(cx: InlineContext, next: number, pos: number): number { + if(next != 33) { + return -1 + } + const link = parseInternalLink(cx, pos + 1) + if(link) { + const embedMark = cx.elt('EmbedMark', pos, pos + 1) + return cx.addElement( + cx.elt('Embed', pos, link.to, [embedMark, link]), + ) + } + return -1 + }, + before: 'Image', + }, + ], +} function parseInternalLink(cx: InlineContext, pos: number): Element | null { - if ( - cx.char(pos) != 91 /* [ */ || - cx.char(pos + 1) != 91 || - !isClosedLink(cx, pos) - ) { - return null; - } - const contents: Element[] = []; - contents.push(cx.elt("InternalMark", pos, pos + 2)); - pos = cx.skipSpace(pos + 2); - const path = parsePath(cx, pos - cx.offset, cx.offset); - if (path) { - contents.push(path); - pos = cx.skipSpace(path.to); - } - const subpath = parseSubpath(cx, pos); - if (subpath) { - contents.push(subpath); - pos = cx.skipSpace(subpath.to); - } - if (path == null && subpath == null) { - return null; - } - if (cx.char(pos) == 124 /* | */) { - contents.push(cx.elt("InternalMark", pos, pos + 1)); - pos += 1; - const display = parseDisplay(cx, pos); - if (display) { - contents.push(display); - pos = cx.skipSpace(display.to); + if( + cx.char(pos) != 91 /* [ */ || + cx.char(pos + 1) != 91 || + !isClosedLink(cx, pos) + ) { + return null } - } - contents.push(cx.elt("InternalMark", pos, pos + 2)); - return cx.elt( - "InternalLink", - contents[0].from, - contents[contents.length - 1].to, - contents, - ); + const contents: Element[] = [] + contents.push(cx.elt('InternalMark', pos, pos + 2)) + pos = cx.skipSpace(pos + 2) + const path = parsePath(cx, pos - cx.offset, cx.offset) + if(path) { + contents.push(path) + pos = cx.skipSpace(path.to) + } + const subpath = parseSubpath(cx, pos) + if(subpath) { + contents.push(subpath) + pos = cx.skipSpace(subpath.to) + } + if(path == null && subpath == null) { + return null + } + if(cx.char(pos) == 124 /* | */) { + contents.push(cx.elt('InternalMark', pos, pos + 1)) + pos += 1 + const display = parseDisplay(cx, pos) + if(display) { + contents.push(display) + pos = cx.skipSpace(display.to) + } + } + contents.push(cx.elt('InternalMark', pos, pos + 2)) + return cx.elt( + 'InternalLink', + contents[0].from, + contents[contents.length - 1].to, + contents, + ) } function isClosedLink(cx: InlineContext, start: number): boolean { - for (let pos = start + 2; pos < cx.end; pos++) { - if (cx.char(pos) == 91 /* [ */ && cx.char(pos + 1) == 91) { - return false; - } else if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) { - // return false for empty - // true otherwise - return pos > start + 2; + for(let pos = start + 2; pos < cx.end; pos++) { + if(cx.char(pos) == 91 /* [ */ && cx.char(pos + 1) == 91) { + return false + } else if(cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) { + // return false for empty + // true otherwise + return pos > start + 2 + } } - } - return false; + return false } function parsePath( - cx: InlineContext, - start: number, - offset: number, + cx: InlineContext, + start: number, + offset: number, ): Element | null { - // anything but: |[]#^\/ - const match = /^[^[\]|#^\\/]+/.exec(cx.text.slice(start)); - if (match) { - return cx.elt( - "InternalPath", - offset + start, - offset + start + match[0].length, - ); - } - return null; + // anything but: |[]#^\/ + const match = /^[^[\]|#^\\/]+/.exec(cx.text.slice(start)) + if(match) { + return cx.elt( + 'InternalPath', + offset + start, + offset + start + match[0].length, + ) + } + return null } function parseSubpath(cx: InlineContext, start: number): Element | null { - if (cx.char(start) != 35 /* # */) { - return null; - } - for (let pos = start + 1; pos < cx.end; pos++) { - if ( - cx.char(pos) == 124 /* | */ || - (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) - ) { - return cx.elt("InternalSubpath", start, pos); + if(cx.char(start) != 35 /* # */) { + return null + } + for(let pos = start + 1; pos < cx.end; pos++) { + if( + cx.char(pos) == 124 /* | */ || + (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) + ) { + return cx.elt('InternalSubpath', start, pos) + } } - } - return null; + return null } function parseDisplay(cx: InlineContext, start: number): Element | null { - for (let pos = start; pos < cx.end; pos++) { - if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) { - if (pos == start) { - return null; - } - return cx.elt("InternalDisplay", start, pos); + for(let pos = start; pos < cx.end; pos++) { + if(cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) { + if(pos == start) { + return null + } + return cx.elt('InternalDisplay', start, pos) + } } - } - return null; + return null } -export const MarkDelim = { resolve: "Mark", mark: "MarkMarker" }; +export const MarkDelim = {resolve: 'Mark', mark: 'MarkMarker'} export const Mark: MarkdownConfig = { - defineNodes: ["Mark", "MarkMarker"], - parseInline: [ - { - name: "Mark", - parse(cx: InlineContext, next: number, pos: number) { - if (next != 61 /* '=' */ || cx.char(pos + 1) != 61) return -1; - return cx.addDelimiter(MarkDelim, pos, pos + 2, true, true); - }, - }, - ], - props: [ - styleTags({ - MarkMarker: tags.meta, - }), - ], -}; + defineNodes: ['Mark', 'MarkMarker'], + parseInline: [ + { + name: 'Mark', + parse(cx: InlineContext, next: number, pos: number) { + if(next != 61 /* '=' */ || cx.char(pos + 1) != 61) return -1 + return cx.addDelimiter(MarkDelim, pos, pos + 2, true, true) + }, + }, + ], + props: [ + styleTags({ + MarkMarker: tags.meta, + }), + ], +} export const InsertDelim: DelimiterType = { - resolve: "Insert", - mark: "InsertMarker", -}; + resolve: 'Insert', + mark: 'InsertMarker', +} export const Insert: MarkdownConfig = { - defineNodes: ["Insert", "InsertMarker"], - parseInline: [ - { - name: "Insert", - parse(cx: InlineContext, next: number, pos: number) { - if (next != 43 /* '+' */ || cx.char(pos + 1) != 43) return -1; - return cx.addDelimiter(InsertDelim, pos, pos + 2, true, true); - }, - }, - ], - props: [ - styleTags({ - InsertMarker: [tags.meta, tags.inserted], - }), - ], -}; + defineNodes: ['Insert', 'InsertMarker'], + parseInline: [ + { + name: 'Insert', + parse(cx: InlineContext, next: number, pos: number) { + if(next != 43 /* '+' */ || cx.char(pos + 1) != 43) return -1 + return cx.addDelimiter(InsertDelim, pos, pos + 2, true, true) + }, + }, + ], + props: [ + styleTags({ + InsertMarker: [tags.meta, tags.inserted], + }), + ], +} /* Copyright (C) 2020 by Marijn Haverbeke and others https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE */ class TaskParser implements LeafBlockParser { - nextLine() { - return false; - } - - finish(cx: BlockContext, leaf: LeafBlock) { - cx.addLeafElement( - leaf, - cx.elt("Task", leaf.start, leaf.start + leaf.content.length, [ - cx.elt("TaskMarker", leaf.start, leaf.start + 3), - ...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3), - ]), - ); - return true; - } + nextLine() { + return false + } + + finish(cx: BlockContext, leaf: LeafBlock) { + cx.addLeafElement( + leaf, + cx.elt('Task', leaf.start, leaf.start + leaf.content.length, [ + cx.elt('TaskMarker', leaf.start, leaf.start + 3), + ...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3), + ]), + ) + return true + } } /// Extension providing @@ -475,188 +476,188 @@ class TaskParser implements LeafBlockParser { /// `[x]` to add a checkbox. /// `x` can be any character export const TaskList: MarkdownConfig = { - defineNodes: [{ name: "Task", block: true }, "TaskMarker"], - parseBlock: [ - { - name: "TaskList", - leaf(cx: BlockContext, leaf: LeafBlock) { - return /^\[.\]/.test(leaf.content) && cx.parentType().name == "ListItem" - ? new TaskParser() - : null; - }, - after: "SetextHeading", - }, - ], -}; + defineNodes: [{name: 'Task', block: true}, 'TaskMarker'], + parseBlock: [ + { + name: 'TaskList', + leaf(cx: BlockContext, leaf: LeafBlock) { + return /^\[.\]/.test(leaf.content) && cx.parentType().name == 'ListItem' + ? new TaskParser() + : null + }, + after: 'SetextHeading', + }, + ], +} /* End Copyright */ -const TexDelim = { resolve: "TexInline", mark: "TexMarker" }; +const TexDelim = {resolve: 'TexInline', mark: 'TexMarker'} export const Tex: MarkdownConfig = { - defineNodes: ["TexBlock", "TexInline", "TexMarker"], - parseBlock: [ - { - name: "TexBlock", - endLeaf: (_, line: Line) => - line.text.slice(line.pos, line.pos + 2) == "$$", - // This is an imperfect match for HyperMD, because - // in HyperMD the block can start even in inline content. - parse(cx: BlockContext, line: Line) { - if (line.text.slice(line.pos, line.pos + 2) != "$$") { - return false; - } - const start = cx.lineStart + line.pos; - const markers = [cx.elt("TexMarker", start, start + 2)]; - const regex = /(^|[^\\])\$\$/; - let remaining = line.text.slice(line.pos + 2); - let startOffset = 2; - let match; - while (!(match = regex.exec(remaining)) && cx.nextLine()) { - remaining = line.text; - startOffset = 0; - } - let end; - if (match) { - const lineEnd = match.index + match[0].length + startOffset; - end = cx.lineStart + lineEnd; - markers.push(cx.elt("TexMarker", end - 2, end)); - if ( - lineEnd == line.text.length || - /^\s+$/.test(line.text.slice(lineEnd)) - ) { - cx.nextLine(); - } else { - line.pos = line.skipSpace(lineEnd); - } - } else { - end = cx.lineStart + line.text.length; - } - cx.addElement(cx.elt("TexBlock", start, end, markers)); - return true; - }, - }, - ], - parseInline: [ - { - name: "TexInline", - parse(cx: InlineContext, next: number, pos: number) { - if (next != 36 /* $ */) { - return -1; - } - const before = cx.slice(pos - 1, pos); - const after = cx.slice(pos + 1, pos + 2); - const canClose = /[^ \t]/.test(before) && !/\d/.test(after); - const canOpen = /[^$ \t]/.test(after); - return cx.addDelimiter(TexDelim, pos, pos + 1, canOpen, canClose); - }, - }, - ], -}; + defineNodes: ['TexBlock', 'TexInline', 'TexMarker'], + parseBlock: [ + { + name: 'TexBlock', + endLeaf: (_, line: Line) => + line.text.slice(line.pos, line.pos + 2) == '$$', + // This is an imperfect match for HyperMD, because + // in HyperMD the block can start even in inline content. + parse(cx: BlockContext, line: Line) { + if(line.text.slice(line.pos, line.pos + 2) != '$$') { + return false + } + const start = cx.lineStart + line.pos + const markers = [cx.elt('TexMarker', start, start + 2)] + const regex = /(^|[^\\])\$\$/ + let remaining = line.text.slice(line.pos + 2) + let startOffset = 2 + let match + while(!(match = regex.exec(remaining)) && cx.nextLine()) { + remaining = line.text + startOffset = 0 + } + let end + if(match) { + const lineEnd = match.index + match[0].length + startOffset + end = cx.lineStart + lineEnd + markers.push(cx.elt('TexMarker', end - 2, end)) + if( + lineEnd == line.text.length || + /^\s+$/.test(line.text.slice(lineEnd)) + ) { + cx.nextLine() + } else { + line.pos = line.skipSpace(lineEnd) + } + } else { + end = cx.lineStart + line.text.length + } + cx.addElement(cx.elt('TexBlock', start, end, markers)) + return true + }, + }, + ], + parseInline: [ + { + name: 'TexInline', + parse(cx: InlineContext, next: number, pos: number) { + if(next != 36 /* $ */) { + return -1 + } + const before = cx.slice(pos - 1, pos) + const after = cx.slice(pos + 1, pos + 2) + const canClose = /[^ \t]/.test(before) && !/\d/.test(after) + const canOpen = /[^$ \t]/.test(after) + return cx.addDelimiter(TexDelim, pos, pos + 1, canOpen, canClose) + }, + }, + ], +} export const YAMLFrontMatter: (options?: { - allBlocks?: boolean; -}) => MarkdownConfig = ({ allBlocks } = {}) => ({ - defineNodes: ["YAMLFrontMatter", "YAMLMarker", "YAMLContent"], - parseBlock: [ - { - name: "YAMLFrontMatter", - before: "FencedCode", - parse(cx: BlockContextWithInput, line: Line) { - // if(cx.insideYaml) { - // return false - // } - const start = cx.lineStart; - - // abort when not at start and not wanting all blocks - if (cx.prevLineEnd() !== -1 && !allBlocks) { - // cx.insideYaml = true - return false; - } - - // const chunk = cx.input.chunk(start) - // line.addMarker() - // ignoring blocks with new lines after the start-line, to not highlight actual content with hr around - if ( - line.text !== "---" || - cx.input.read(start + 3, start + 3 + 2) === "\n\n" - ) - return false; - - const lastLineEnd = cx.input.read( - cx.prevLineEnd() - 1, - cx.prevLineEnd() + 1, - ); - - if ( - cx.prevLineEnd() === -1 || - lastLineEnd === "\n\n" || - lastLineEnd === "\r\n\r\n" - ) { - // cx.insideYaml = true - - // todo: improve this check, when just using `nextLine` this will overwrite anything afterwards - // but `---` is also for hr, thus may be existing just once - // and currently does not correctly work when changing only one marker, as incrementally read seems to be lost - const contentToEnd = cx.input.read(start, cx.input.length); - const hasEndLine = contentToEnd.indexOf("\n---\n"); - - if (hasEndLine === -1) return false; - let end: number | undefined; - do { - if ( - cx.lineStart !== start && - cx.input.chunk(cx.lineStart) === "---" - ) { - end = cx.lineStart; + allBlocks?: boolean; +}) => MarkdownConfig = ({allBlocks} = {}) => ({ + defineNodes: ['YAMLFrontMatter', 'YAMLMarker', 'YAMLContent'], + parseBlock: [ + { + name: 'YAMLFrontMatter', + before: 'FencedCode', + parse(cx: BlockContextWithInput, line: Line) { + // if(cx.insideYaml) { + // return false + // } + const start = cx.lineStart + + // abort when not at start and not wanting all blocks + if(cx.prevLineEnd() !== -1 && !allBlocks) { + // cx.insideYaml = true + return false + } + + // const chunk = cx.input.chunk(start) + // line.addMarker() + // ignoring blocks with new lines after the start-line, to not highlight actual content with hr around + if( + line.text !== '---' || + cx.input.read(start + 3, start + 3 + 2) === '\n\n' + ) + return false + + const lastLineEnd = cx.input.read( + cx.prevLineEnd() - 1, + cx.prevLineEnd() + 1, + ) + + if( + cx.prevLineEnd() === -1 || + lastLineEnd === '\n\n' || + lastLineEnd === '\r\n\r\n' + ) { + // cx.insideYaml = true + + // todo: improve this check, when just using `nextLine` this will overwrite anything afterwards + // but `---` is also for hr, thus may be existing just once + // and currently does not correctly work when changing only one marker, as incrementally read seems to be lost + const contentToEnd = cx.input.read(start, cx.input.length) + const hasEndLine = contentToEnd.indexOf('\n---\n') + + if(hasEndLine === -1) return false + let end: number | undefined + do { + if( + cx.lineStart !== start && + cx.input.chunk(cx.lineStart) === '---' + ) { + end = cx.lineStart + } + } while(typeof end === 'undefined' && cx.nextLine()) + + if(typeof end === 'undefined') { + return false + } + + if(allBlocks) { + // just kept this marker as not sure how exactly nested langs work, + // is sharing the cx while iterating above, then it may be needed + // cx.insideYaml = false + } + + cx.addElement( + cx.elt('YAMLFrontMatter', start, end, [ + cx.elt('YAMLMarker', start, start + 3), + cx.elt('YAMLContent', start + 4, end - 1), + cx.elt('YAMLMarker', end, end + 3), + ]), + ) + return true + } + return false + }, + }, + ], + props: [ + styleTags({ + YAMLMarker: [tags.strong, tags.blockComment], + YAMLContent: tags.blockComment, + }), + foldNodeProp.add({ + YAMLFrontMatter(tree) { + const first = tree.firstChild, + last = tree.lastChild! + if(!first || first.name != 'YAMLMarker') return null + return { + from: first.to, + to: last.name == 'YAMLMarker' ? last.to : tree.to, + } + }, + }), + ], + wrap: parseMixed((node) => { + return node.name === 'YAMLContent' + ? { + parser: StreamLanguage.define(yaml).parser, + // overlay: node => node.type.name == 'Text', } - } while (typeof end === "undefined" && cx.nextLine()); - - if (typeof end === "undefined") { - return false; - } - - if (allBlocks) { - // just kept this marker as not sure how exactly nested langs work, - // is sharing the cx while iterating above, then it may be needed - // cx.insideYaml = false - } - - cx.addElement( - cx.elt("YAMLFrontMatter", start, end, [ - cx.elt("YAMLMarker", start, start + 3), - cx.elt("YAMLContent", start + 4, end - 1), - cx.elt("YAMLMarker", end, end + 3), - ]), - ); - return true; - } - return false; - }, - }, - ], - props: [ - styleTags({ - YAMLMarker: [tags.strong, tags.blockComment], - YAMLContent: tags.blockComment, + : null }), - foldNodeProp.add({ - YAMLFrontMatter(tree) { - const first = tree.firstChild, - last = tree.lastChild!; - if (!first || first.name != "YAMLMarker") return null; - return { - from: first.to, - to: last.name == "YAMLMarker" ? last.to : tree.to, - }; - }, - }), - ], - wrap: parseMixed((node) => { - return node.name === "YAMLContent" - ? { - parser: StreamLanguage.define(yaml).parser, - // overlay: node => node.type.name == 'Text', - } - : null; - }), -}); +}) diff --git a/apps/sandbox/src/main.tsx b/apps/sandbox/src/main.tsx index 1c6ec56..f33d1aa 100644 --- a/apps/sandbox/src/main.tsx +++ b/apps/sandbox/src/main.tsx @@ -1,7 +1,7 @@ -import React from 'react' +import React, { useState } from 'react' import { - contentUIDecorators, - ContentLeafsProvider, + contentUIDecorators, + ContentLeafsProvider, } from '@content-ui/react/ContentLeafsContext' import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles' import CssBaseline from '@mui/material/CssBaseline' @@ -18,30 +18,42 @@ const root = ReactDOM.createRoot(rootElement) const themes = customTheme() const contentUIMapping: typeof renderMapping = { - ...renderMapping, - leafs: { - ...renderMapping.leafs, - }, - components: { - ...renderMapping.components, - Code: CustomCodeMirror, - }, + ...renderMapping, + leafs: { + ...renderMapping.leafs, + }, + components: { + ...renderMapping.components, + Code: CustomCodeMirror, + }, } -root.render( - - - - - +const Main = () => { + const [theme, setTheme] = useState<'dark' | 'light'>( + () => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', + ) + + const toggleTheme = () => { + setTheme(t => t === 'dark' ? 'light' : 'dark') + } + + return + + - + - - - + + +} + +root.render( + + +
+ , ) diff --git a/apps/sandbox/src/pages/PageHome.tsx b/apps/sandbox/src/pages/PageHome.tsx index 03364dc..b17c3a7 100644 --- a/apps/sandbox/src/pages/PageHome.tsx +++ b/apps/sandbox/src/pages/PageHome.tsx @@ -1,6 +1,7 @@ -import { Container, Paper } from "@mui/material"; -import { ViewerFromText } from "@content-ui/md-mui/Viewer"; -import { ContentParser } from "@content-ui/md/parser/ContentParser"; +import Container from '@mui/material/Container' +import Paper from '@mui/material/Paper' +import { ViewerFromText } from '@content-ui/md-mui/Viewer' +import { ContentParser } from '@content-ui/md/parser/ContentParser' const md = `# Content-UI Demo @@ -28,14 +29,14 @@ Lorem ipsum __breaking **line**__. > ⚠️ Warning Note: Experimental ⚗️ Last line in text. -`; +` export const PageHome = () => { - return ( - - - - - - ); -}; + return ( + + + + + + ) +} diff --git a/apps/sandbox/src/pages/PageInput.tsx b/apps/sandbox/src/pages/PageInput.tsx index 89cd99f..43941ae 100644 --- a/apps/sandbox/src/pages/PageInput.tsx +++ b/apps/sandbox/src/pages/PageInput.tsx @@ -10,8 +10,8 @@ import { SettingsProvider } from '@content-ui/react/LeafSettings' import { useContentEditor } from '@content-ui/input/useContentEditor' import { useContent } from '@content-ui/react/useContent' import { ContentFileProvider } from '@content-ui/react/ContentFileContext' -import useTheme from '@mui/material/styles/useTheme' -import { useMediaQuery } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import useMediaQuery from '@mui/material/useMediaQuery' const md = `# About a Note diff --git a/apps/sandbox/src/theme.ts b/apps/sandbox/src/theme.ts index eb7b636..6fed765 100644 --- a/apps/sandbox/src/theme.ts +++ b/apps/sandbox/src/theme.ts @@ -1,223 +1,223 @@ -import { getContrastRatio, createTheme, Theme } from "@mui/material/styles"; -import { ThemeOptions } from "@mui/material/styles/createTheme"; -import { TypographyOptionsWithExtras } from "@content-ui/md-mui/MuiComponents/Theme"; +import { getContrastRatio, createTheme, Theme } from '@mui/material/styles' +import { ThemeOptions } from '@mui/material/styles/createTheme' +import { TypographyOptionsWithExtras } from '@content-ui/md-mui/MuiComponents/Theme' export const fontHeading = - '"Roboto Slab", "Playfair Display", Didot, Georgia, "Times New Roman", Times, serif'; + '"Roboto Slab", "Playfair Display", Didot, Georgia, "Times New Roman", Times, serif' export const fontBody = - "Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif"; + 'Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif' export const fontMono = - '"Fira Code", "SF Mono", "Segoe UI Mono", Menlo, Consolas , Monaco, Liberation Mono, Lucida Console, Courier, monospace'; -const universal: Pick & { - typography: TypographyOptionsWithExtras; + '"Fira Code", "SF Mono", "Segoe UI Mono", Menlo, Consolas , Monaco, Liberation Mono, Lucida Console, Courier, monospace' +const universal: Pick & { + typography: TypographyOptionsWithExtras; } = { - palette: { - //contrastThreshold: 2, - }, - breakpoints: { - values: { - xs: 0, - sm: 543, - // md: 768, - md: 940, - lg: 1180, - xl: 1920, - }, - }, - typography: { - fontSize: 14, - fontFamily: fontBody, - fontFamilyCode: fontMono, - fontSizeCode: "0.875em", - h1: { - fontFamily: fontHeading, - fontSize: "2.45rem", - }, - h2: { - fontFamily: fontHeading, - fontSize: "2.115rem", - }, - h3: { - fontFamily: fontHeading, - fontSize: "1.95rem", - }, - h4: { - fontFamily: fontHeading, - fontSize: "1.75rem", - }, - h5: { - fontFamily: fontHeading, - fontSize: "1.615rem", - }, - h6: { - fontFamily: fontHeading, - fontSize: "1.25rem", - }, - body1: { - fontFamily: fontBody, - fontSize: "1.0125rem", - letterSpacing: "0.0195em", - }, - body2: { - fontFamily: fontBody, - fontSize: "0.95rem", - letterSpacing: "0.021em", - }, - subtitle1: { - fontFamily: fontHeading, - fontSize: "1.25rem", - }, - subtitle2: { - fontFamily: fontHeading, - fontSize: "1rem", + palette: { + //contrastThreshold: 2, + }, + breakpoints: { + values: { + xs: 0, + sm: 543, + // md: 768, + md: 940, + lg: 1180, + xl: 1920, + }, }, - caption: { - fontSize: "0.8134rem", + typography: { + fontSize: 14, + fontFamily: fontBody, + fontFamilyCode: fontMono, + fontSizeCode: '0.875em', + h1: { + fontFamily: fontHeading, + fontSize: '2.45rem', + }, + h2: { + fontFamily: fontHeading, + fontSize: '2.115rem', + }, + h3: { + fontFamily: fontHeading, + fontSize: '1.95rem', + }, + h4: { + fontFamily: fontHeading, + fontSize: '1.75rem', + }, + h5: { + fontFamily: fontHeading, + fontSize: '1.615rem', + }, + h6: { + fontFamily: fontHeading, + fontSize: '1.25rem', + }, + body1: { + fontFamily: fontBody, + fontSize: '1.0125rem', + letterSpacing: '0.0195em', + }, + body2: { + fontFamily: fontBody, + fontSize: '0.95rem', + letterSpacing: '0.021em', + }, + subtitle1: { + fontFamily: fontHeading, + fontSize: '1.25rem', + }, + subtitle2: { + fontFamily: fontHeading, + fontSize: '1rem', + }, + caption: { + fontSize: '0.8134rem', + }, }, - }, - /*shape: { - borderRadius: 0, - },*/ -}; + /*shape: { + borderRadius: 0, + },*/ +} export const customTheme = (): { - dark: Theme; - light: Theme; + dark: Theme; + light: Theme; } => { - const getContrastText = (background: string) => { - const contrastText = - getContrastRatio(background, "#c6c4c4") >= 2 - ? getContrastRatio(background, "#c6c4c4") <= 3 - ? "#ffffff" - : "#c6c4c4" - : getContrastRatio(background, "#001f29") <= 3 - ? "#000000" - : "#001f29"; + const getContrastText = (background: string) => { + const contrastText = + getContrastRatio(background, '#c6c4c4') >= 2 + ? getContrastRatio(background, '#c6c4c4') <= 3 + ? '#ffffff' + : '#c6c4c4' + : getContrastRatio(background, '#001f29') <= 3 + ? '#000000' + : '#001f29' - // @ts-ignore - if (process.env.NODE_ENV !== "production") { - const contrast = getContrastRatio(background, contrastText); - if (contrast < 3) { - console.error( - [ - `MUI: The contrast ratio of ${contrast}:1 for ${contrastText} on ${background}`, - "falls below the WCAG recommended absolute minimum contrast ratio of 3:1.", - "https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast", - ].join("\n") - ); - } - } + // @ts-ignore + if(process.env.NODE_ENV !== 'production') { + const contrast = getContrastRatio(background, contrastText) + if(contrast < 3) { + console.error( + [ + `MUI: The contrast ratio of ${contrast}:1 for ${contrastText} on ${background}`, + 'falls below the WCAG recommended absolute minimum contrast ratio of 3:1.', + 'https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast', + ].join('\n'), + ) + } + } - return contrastText; - }; - const themeDark = createTheme({ - ...universal, - palette: { - ...universal.palette, - mode: "dark", - primary: { - main: "#05aeca", - dark: "#033944", - }, - secondary: { - main: "#bd90e0", - }, - background: { - paper: "#04212a", - default: "#011217", - }, - text: { - primary: "#c6c4c4", - secondary: "#acc9c5", - }, - info: { - main: "#1872b9", - }, - error: { - main: "#e82c2c", - //main: '#b71c10', - }, - warning: { - main: "#d54600", - }, - action: { - hoverOpacity: 0.2, - }, - getContrastText: getContrastText, - //divider: 'rgba(65, 194, 194, 0.24)', - }, - components: { - MuiPaper: { - styleOverrides: { root: { backgroundImage: "unset" } }, - }, - MuiInputLabel: { - styleOverrides: { - root: { - "&$error": { - color: "#e82c2c", - }, - }, - }, - }, - /*MuiTextField: { - defaultProps: {size: 'small'}, - },*/ - }, - }); + return contrastText + } + const themeDark = createTheme({ + ...universal, + palette: { + ...universal.palette, + mode: 'dark', + primary: { + main: '#05aeca', + dark: '#033944', + }, + secondary: { + main: '#bd90e0', + }, + background: { + paper: '#04212a', + default: '#011217', + }, + text: { + primary: '#c6c4c4', + secondary: '#acc9c5', + }, + info: { + main: '#1872b9', + }, + error: { + main: '#e82c2c', + //main: '#b71c10', + }, + warning: { + main: '#d54600', + }, + action: { + hoverOpacity: 0.2, + }, + getContrastText: getContrastText, + //divider: 'rgba(65, 194, 194, 0.24)', + }, + components: { + MuiPaper: { + styleOverrides: {root: {backgroundImage: 'unset'}}, + }, + MuiInputLabel: { + styleOverrides: { + root: { + '&$error': { + color: '#e82c2c', + }, + }, + }, + }, + /*MuiTextField: { + defaultProps: {size: 'small'}, + },*/ + }, + }) - const themeLight = createTheme({ - ...universal, - palette: { - ...universal.palette, - mode: "light", - primary: { - main: "#0590a7", - dark: "#033944", - }, - secondary: { - main: "#513793", - }, - background: { - paper: "#eeeeee", - default: "#e4e4e4", - }, - text: { - primary: "#001f29", - secondary: "#001820", - }, - success: { - main: "#50a82d", - }, - warning: { - dark: "#cc4c00", - main: "#f05a00", - }, - info: { - main: "#3593dd", - }, - action: { - hoverOpacity: 0.2, - }, - //divider: 'rgba(50, 153, 143, 0.4)', - getContrastText: getContrastText, - }, - components: { - MuiAlert: { - styleOverrides: { - /*standardSuccess: { - backgroundColor: 'green', - color: 'white', - },*/ - outlinedSuccess: { - color: "#25790b", - }, - }, - }, - }, - }); + const themeLight = createTheme({ + ...universal, + palette: { + ...universal.palette, + mode: 'light', + primary: { + main: '#0590a7', + dark: '#033944', + }, + secondary: { + main: '#513793', + }, + background: { + paper: '#eeeeee', + default: '#e4e4e4', + }, + text: { + primary: '#001f29', + secondary: '#001820', + }, + success: { + main: '#50a82d', + }, + warning: { + dark: '#cc4c00', + main: '#f05a00', + }, + info: { + main: '#3593dd', + }, + action: { + hoverOpacity: 0.2, + }, + //divider: 'rgba(50, 153, 143, 0.4)', + getContrastText: getContrastText, + }, + components: { + MuiAlert: { + styleOverrides: { + /*standardSuccess: { + backgroundColor: 'green', + color: 'white', + },*/ + outlinedSuccess: { + color: '#25790b', + }, + }, + }, + }, + }) - return { - dark: themeDark, - light: themeLight, - }; -}; + return { + dark: themeDark, + light: themeLight, + } +} From 827d7ef56db9dd240d3b741c73d7bf1a325c798d Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 4 Oct 2024 22:53:29 +0200 Subject: [PATCH 6/6] v up; --- apps/sandbox/package.json | 10 ++++---- package-lock.json | 50 ++++++++++++++++++------------------ packages/input/package.json | 10 ++++---- packages/md-mui/package.json | 10 ++++---- packages/react/package.json | 2 +- packages/struct/package.json | 2 +- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/sandbox/package.json b/apps/sandbox/package.json index bfc819b..cd301e6 100644 --- a/apps/sandbox/package.json +++ b/apps/sandbox/package.json @@ -1,6 +1,6 @@ { "name": "content-ui-sandbox", - "version": "0.0.11", + "version": "0.1.0", "description": "Content-UI MD-MUI demo", "keywords": [ "typescript", @@ -28,11 +28,11 @@ "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", - "@content-ui/input": "0.0.7", + "@content-ui/input": "0.1.0", "@content-ui/md": "0.0.8", - "@content-ui/md-mui": "0.0.12", - "@content-ui/react": "0.0.11", - "@content-ui/struct": "0.0.2", + "@content-ui/md-mui": "0.1.0", + "@content-ui/react": "0.1.0", + "@content-ui/struct": "0.1.0", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.10", diff --git a/package-lock.json b/package-lock.json index 07687ac..35aefbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -119,7 +119,7 @@ }, "apps/sandbox": { "name": "content-ui-sandbox", - "version": "0.0.11", + "version": "0.1.0", "license": "MIT", "dependencies": { "@codemirror/commands": "^6.0.0", @@ -135,11 +135,11 @@ "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", - "@content-ui/input": "0.0.7", + "@content-ui/input": "0.1.0", "@content-ui/md": "0.0.8", - "@content-ui/md-mui": "0.0.12", - "@content-ui/react": "0.0.11", - "@content-ui/struct": "0.0.2", + "@content-ui/md-mui": "0.1.0", + "@content-ui/react": "0.1.0", + "@content-ui/struct": "0.1.0", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.10", @@ -20776,12 +20776,12 @@ }, "packages/input": { "name": "@content-ui/input", - "version": "0.0.7", + "version": "0.1.0", "license": "MIT", "devDependencies": { "@codemirror/state": "^6.0.0", - "@content-ui/md-mui": "*", - "@content-ui/react": "*", + "@content-ui/md-mui": "~0.1.0", + "@content-ui/react": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", @@ -20792,8 +20792,8 @@ }, "peerDependencies": { "@codemirror/state": "^6.0.0", - "@content-ui/md-mui": "*", - "@content-ui/react": "*", + "@content-ui/md-mui": "~0.1.0", + "@content-ui/react": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", @@ -20840,23 +20840,23 @@ }, "packages/md-mui": { "name": "@content-ui/md-mui", - "version": "0.0.12", + "version": "0.1.0", "license": "MIT", "dependencies": { "react-router-dom": "^6.3.0", "yaml": "^2.1.1" }, "devDependencies": { - "@content-ui/react": "*", - "@content-ui/struct": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/struct": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0" }, "peerDependencies": { - "@content-ui/react": "*", - "@content-ui/struct": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/struct": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "react": "^17.0 || ^18.0", @@ -20885,7 +20885,7 @@ }, "packages/react": { "name": "@content-ui/react", - "version": "0.0.11", + "version": "0.1.0", "license": "MIT", "dependencies": { "vfile": "^6.0.2" @@ -20903,7 +20903,7 @@ }, "packages/struct": { "name": "@content-ui/struct", - "version": "0.0.2", + "version": "0.1.0", "license": "MIT", "dependencies": { "mdast-util-definition-list": "^2.0.0" @@ -22582,8 +22582,8 @@ "version": "file:packages/input", "requires": { "@codemirror/state": "^6.0.0", - "@content-ui/md-mui": "*", - "@content-ui/react": "*", + "@content-ui/md-mui": "~0.1.0", + "@content-ui/react": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "@ui-controls/progress": "~0.0.4", @@ -22633,8 +22633,8 @@ "@content-ui/md-mui": { "version": "file:packages/md-mui", "requires": { - "@content-ui/react": "*", - "@content-ui/struct": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/struct": "~0.1.0", "@mui/icons-material": "^5.10", "@mui/material": "^5.1", "react": "^17.0 || ^18.0", @@ -26312,11 +26312,11 @@ "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", - "@content-ui/input": "0.0.7", + "@content-ui/input": "0.1.0", "@content-ui/md": "0.0.8", - "@content-ui/md-mui": "0.0.12", - "@content-ui/react": "0.0.11", - "@content-ui/struct": "0.0.2", + "@content-ui/md-mui": "0.1.0", + "@content-ui/react": "0.1.0", + "@content-ui/struct": "0.1.0", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.10", diff --git a/packages/input/package.json b/packages/input/package.json index 60a6707..2d814a8 100644 --- a/packages/input/package.json +++ b/packages/input/package.json @@ -1,6 +1,6 @@ { "name": "@content-ui/input", - "version": "0.0.7", + "version": "0.1.0", "description": "", "author": { "name": "bemit", @@ -24,8 +24,8 @@ "@ui-schema/kit-codemirror": "0.1.0-alpha.1 || ~0.1.1", "react-progress-state": "~0.2.6 || ~0.3.1", "@ui-controls/progress": "~0.0.4", - "@content-ui/react": "*", - "@content-ui/md-mui": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/md-mui": "~0.1.0", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0", "@codemirror/state": "^6.0.0" @@ -36,8 +36,8 @@ "@ui-schema/kit-codemirror": "~0.1.1", "react-progress-state": "~0.3.1", "@ui-controls/progress": "~0.0.4", - "@content-ui/react": "*", - "@content-ui/md-mui": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/md-mui": "~0.1.0", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0", "@codemirror/state": "^6.0.0" diff --git a/packages/md-mui/package.json b/packages/md-mui/package.json index 00991ef..bdc3db5 100644 --- a/packages/md-mui/package.json +++ b/packages/md-mui/package.json @@ -1,6 +1,6 @@ { "name": "@content-ui/md-mui", - "version": "0.0.12", + "version": "0.1.0", "description": "", "author": { "name": "bemit", @@ -25,16 +25,16 @@ "peerDependencies": { "@mui/icons-material": "^5.10", "@mui/material": "^5.1", - "@content-ui/react": "*", - "@content-ui/struct": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/struct": "~0.1.0", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0" }, "devDependencies": { "@mui/icons-material": "^5.10", "@mui/material": "^5.1", - "@content-ui/react": "*", - "@content-ui/struct": "*", + "@content-ui/react": "~0.1.0", + "@content-ui/struct": "~0.1.0", "react": "^17.0 || ^18.0", "react-dom": "^17.0 || ^18.0" }, diff --git a/packages/react/package.json b/packages/react/package.json index 0a47810..bc1f8ab 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@content-ui/react", - "version": "0.0.11", + "version": "0.1.0", "description": "", "author": { "name": "bemit", diff --git a/packages/struct/package.json b/packages/struct/package.json index c6bb83b..e383ef3 100644 --- a/packages/struct/package.json +++ b/packages/struct/package.json @@ -1,6 +1,6 @@ { "name": "@content-ui/struct", - "version": "0.0.2", + "version": "0.1.0", "description": "", "author": { "name": "bemit",