Skip to content

Commit

Permalink
feat(editor): improve documentation editor using Remirror (datahub-pr…
Browse files Browse the repository at this point in the history
…oject#6631)

Co-authored-by: Chris Collins <[email protected]>
  • Loading branch information
2 people authored and Eric Yomi committed Jan 18, 2023
1 parent 834369c commit d227b6c
Show file tree
Hide file tree
Showing 36 changed files with 4,031 additions and 92 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
- name: Gradle build (and test)
run: |
${{ matrix.command }}
env:
NODE_OPTIONS: "--max-old-space-size=3072"
- uses: actions/upload-artifact@v3
if: always()
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/docker-unified.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ jobs:
run: |
./gradlew :datahub-frontend:dist -x test -x yarnTest -x yarnLint --parallel
mv ./datahub-frontend/build/distributions/datahub-frontend-*.zip datahub-frontend.zip
env:
NODE_OPTIONS: "--max-old-space-size=3072"
- name: Build and push
uses: ./.github/actions/docker-custom-build-and-push
with:
Expand Down
10 changes: 10 additions & 0 deletions datahub-web-react/craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,14 @@ module.exports = {
},
},
],
jest: {
configure: (jestConfig) => {
jestConfig.transformIgnorePatterns = [
// Ensures that lib0 and y-protocol libraries are transformed through babel as well
'node_modules/(?!(lib0|y-protocols)).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$',
];
return jestConfig;
},
},
};
6 changes: 6 additions & 0 deletions datahub-web-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"@miragejs/graphql": "^0.1.11",
"@monaco-editor/react": "^4.3.1",
"@react-hook/window-size": "^3.0.7",
"@remirror/pm": "^2.0.3",
"@remirror/react": "^2.0.24",
"@remirror/styles": "^2.0.3",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@tommoor/remove-markdown": "^0.3.2",
Expand Down Expand Up @@ -53,6 +56,7 @@
"miragejs": "^0.1.41",
"moment-timezone": "^0.5.35",
"monaco-editor": "^0.28.1",
"prosemirror-autocomplete": "^0.4.3",
"query-string": "^6.13.8",
"rc-table": "^7.13.1",
"react": "^17.0.0",
Expand All @@ -69,9 +73,11 @@
"react-syntax-highlighter": "^15.4.4",
"react-visibility-sensor": "^5.1.1",
"reactour": "1.18.7",
"remirror": "^2.0.23",
"sinon": "^11.1.1",
"start-server-and-test": "1.12.2",
"styled-components": "^5.2.1",
"turndown-plugin-gfm": "^1.0.2",
"typescript": "^4.1.3",
"uuid": "^8.3.2",
"virtualizedtableforantd4": "^1.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { FetchResult } from '@apollo/client';
import { UpdateDatasetMutation } from '../../../../../../graphql/dataset.generated';
import UpdateDescriptionModal from '../../../../shared/components/legacy/DescriptionModal';
import StripMarkdownText, { removeMarkdown } from '../../../../shared/components/styled/StripMarkdownText';
import MarkdownViewer from '../../../../shared/components/legacy/MarkdownViewer';
import SchemaEditableContext from '../../../../../shared/SchemaEditableContext';
import { useEntityData } from '../../../../shared/EntityContext';
import analytics, { EventType, EntityActionType } from '../../../../../analytics';
import { Editor } from '../../../../shared/tabs/Documentation/components/editor/Editor';

const EditIcon = styled(EditOutlined)`
cursor: pointer;
Expand Down Expand Up @@ -56,12 +56,6 @@ const DescriptionContainer = styled.div`
}
}
`;

const DescriptionText = styled(MarkdownViewer)`
padding-right: 8px;
display: block;
`;

const EditedLabel = styled(Typography.Text)`
position: absolute;
right: -10px;
Expand All @@ -74,6 +68,15 @@ const ReadLessText = styled(Typography.Link)`
margin-right: 4px;
`;

const StyledViewer = styled(Editor)`
padding-right: 8px;
display: block;
.remirror-editor.ProseMirror {
padding: 0;
}
`;

type Props = {
description: string;
original?: string | null;
Expand Down Expand Up @@ -128,7 +131,7 @@ export default function DescriptionField({ description, onUpdate, isEdited = fal
<DescriptionContainer>
{expanded ? (
<>
{!!description && <DescriptionText source={description} />}
{!!description && <StyledViewer content={description} readOnly />}
{!!description && (
<ExpandedActions>
{overLimit && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Typography, Modal, Button, Form } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import MDEditor from '@uiw/react-md-editor';

const DescriptionMarkdown = styled(MDEditor.Markdown)`
padding: 4px 10px;
`;
import { Editor } from '../../tabs/Documentation/components/editor/Editor';
import { ANTD_GRAY } from '../../constants';

const FormLabel = styled(Typography.Text)`
font-size: 10px;
font-weight: bold;
`;

const MarkDownHelpLink = styled(Typography.Link)`
position: absolute;
right: 0;
top: -18px;
font-size: 12px;
const StyledEditor = styled(Editor)`
border: 1px solid ${ANTD_GRAY[4.5]};
`;

const StyledViewer = styled(Editor)`
.remirror-editor.ProseMirror {
padding: 0;
}
`;

type Props = {
Expand Down Expand Up @@ -48,36 +48,12 @@ export default function UpdateDescriptionModal({ title, description, original, o
}
>
<Form layout="vertical">
{isAddDesc ? (
<Form.Item>
<MarkDownHelpLink href="https://joplinapp.org/markdown" target="_blank" type="secondary">
markdown supported
</MarkDownHelpLink>
<MDEditor
style={{ fontWeight: 400 }}
value={updatedDesc}
onChange={(v) => setDesc(v || '')}
preview="live"
height={400}
/>
</Form.Item>
) : (
<Form.Item>
<MarkDownHelpLink href="https://joplinapp.org/markdown" target="_blank" type="secondary">
markdown supported
</MarkDownHelpLink>
<MDEditor
style={{ fontWeight: 400 }}
value={updatedDesc}
onChange={(v) => setDesc(v || '')}
preview="live"
height={400}
/>
</Form.Item>
)}
<Form.Item>
<StyledEditor content={updatedDesc} onChange={setDesc} />
</Form.Item>
{!isAddDesc && description && original && (
<Form.Item label={<FormLabel>Original:</FormLabel>}>
<DescriptionMarkdown source={original || ''} />
<StyledViewer content={original || ''} readOnly />
</Form.Item>
)}
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { Button, Divider, Typography } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import MDEditor from '@uiw/react-md-editor';
import DOMPurify from 'dompurify';

import TabToolbar from '../../components/styled/TabToolbar';
import { AddLinkModal } from '../../components/styled/AddLinkModal';
Expand All @@ -16,12 +14,12 @@ import { LinkList } from './components/LinkList';

import { useEntityData, useRefetch, useRouteToTab } from '../../EntityContext';
import { EDITED_DESCRIPTIONS_CACHE_NAME } from '../../utils';
import { Editor } from './components/editor/Editor';

const DocumentationContainer = styled.div`
margin: 0 auto;
margin: 0 32px;
padding: 40px 0;
max-width: calc(100% - 10px);
margin: 0 32px;
`;

interface Props {
Expand All @@ -33,7 +31,6 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
const { urn, entityData } = useEntityData();
const refetch = useRefetch();
const description = entityData?.editableProperties?.description || entityData?.properties?.description || '';
const sanitizedDescription = DOMPurify.sanitize(description);
const links = entityData?.institutionalMemory?.elements || [];
const localStorageDictionary = localStorage.getItem(EDITED_DESCRIPTIONS_CACHE_NAME);

Expand All @@ -53,7 +50,7 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
</>
) : (
<>
{sanitizedDescription || links.length ? (
{description || links.length ? (
<>
<TabToolbar>
<div>
Expand All @@ -66,15 +63,19 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
{!hideLinksButton && <AddLinkModal buttonProps={{ type: 'text' }} refetch={refetch} />}
</div>
</TabToolbar>
<DocumentationContainer>
{sanitizedDescription ? (
<MDEditor.Markdown style={{ fontWeight: 400 }} source={sanitizedDescription} />
<div>
{description ? (
<Editor content={description} readOnly />
) : (
<Typography.Text type="secondary">No documentation added yet.</Typography.Text>
<DocumentationContainer>
<Typography.Text type="secondary">No documentation added yet.</Typography.Text>
</DocumentationContainer>
)}
<Divider />
{!hideLinksButton && <LinkList refetch={refetch} />}
</DocumentationContainer>
<DocumentationContainer>
{!hideLinksButton && <LinkList refetch={refetch} />}
</DocumentationContainer>
</div>
</>
) : (
<EmptyTab tab="documentation">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { htmlToMarkdown } from '../../../../components/editor/extensions/htmlToMarkdown';

const cases = [
['strike', '<strike>Lorem ipsum</strike>', '~Lorem ipsum~'],
['s', '<s>Lorem ipsum</s>', '~Lorem ipsum~'],
['del', '<del>Lorem ipsum</del>', '~Lorem ipsum~'],
[
'should replace blank nodes in table',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><p>Lorem</p><p>ipsum</p></td></tr></tbody></table>',
'| Lorem ipsum |\n| --- |\n| Lorem<br />ipsum |',
],
['should replace empty p tags to line breaks', '<p>Lorem</p><p></p><p>ipsum</p>', 'Lorem\n\n&nbsp;\n\nipsum'],
[
'should parse image if it does not have a width attribute',
'<img src="/my-image.png" style="width: 100%; min-width: 50px; object-fit: contain;" alt="my image">',
'![my image](/my-image.png)',
],

[
'should highlight code block with html',
'<pre><code class="language language-html">&lt;<span class="pl-ent">p</span>&gt;Hello world&lt;/<span class="pl-ent">p</span>&gt;</code></pre>',
'```html\n<p>Hello world</p>\n```',
],
[
'should highlight code block with js',
'<pre><code class="lang lang-js">;(<span class="pl-k">function</span> () {})()</code></pre>',
'```js\n;(function () {})()\n```',
],
[
'should highlighted remirror code block',
'<pre><code data-code-block-language="ts">;(<span class="pl-k">function</span> () {})()</code></pre>',
'```ts\n;(function () {})()\n```',
],
[
'should parse without language code block',
'<pre><code>;(<span class="pl-k">function</span> () {})()</code></pre>',
'```\n;(function () {})()\n```',
],
[
'should parse datahub mention',
'<span class="mentions" data-datahub-mention-urn="urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)">@SampleHiveDataset</span>',
'[@SampleHiveDataset](urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD))',
],
];

const skipParseCases = [
[
'table if ul or li is present',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><ul><li>Lorem</li><li>ipsum</li></ul></td></tr></tbody></table>',
],
[
'table if present',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><ul><li>Lorem</li><li>ipsum</li></ul></td></tr></tbody></table>',
],
[
'image if it has a width attribute',
'<img src="/my-image.png" style="width: 100%; min-width: 50px; object-fit: contain;" alt="my image" width="200">',
],
];

describe('htmlToMarkdown', () => {
it.each(cases)('%s', (_, input, expected) => {
expect(htmlToMarkdown(input)).toBe(expected);
});

it.each(skipParseCases)('should skip parsing %s', (_, input) => {
expect(htmlToMarkdown(input)).toBe(input);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { markdownToHtml } from '../../../../components/editor/extensions/markdownToHtml';

const cases = [
[
'should parse datahub mentions',
'Lorem [@SampleHiveDataset](urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)) ipsum',
'<p>Lorem <span data-datahub-mention-urn="urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)">@SampleHiveDataset</span> ipsum</p>\n',
],
['should not parse github mentions', 'Lorem @githubuser ipsum', '<p>Lorem @githubuser ipsum</p>\n'],
[
'should parse invalid mentions as links',
'Lorem [@Some link](/lorem-ipsum) ipsum',
'<p>Lorem <a href="/lorem-ipsum">@Some link</a> ipsum</p>\n',
],
];

describe('markdownToHtml', () => {
it.each(cases)('%s', (_, input, expected) => {
expect(markdownToHtml(input)).toBe(expected);
});
});
Loading

0 comments on commit d227b6c

Please sign in to comment.