Skip to content

Commit

Permalink
Merge pull request #1582 from SenseNet/feature/html-syntax-highligths
Browse files Browse the repository at this point in the history
Html Syntax-Highligths for LongText Field
  • Loading branch information
hassanad94 authored Dec 1, 2023
2 parents f072985 + bc9b74e commit e194979
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 3 deletions.
16 changes: 16 additions & 0 deletions apps/sensenet/src/components/field-controls/html-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @module FieldControls
*/

import { useTheme } from '@material-ui/core'
import { ReactClientFieldSetting, HtmlEditor as SnHtmlEditor } from '@sensenet/controls-react'
import React from 'react'

/**
* Field control that represents a LongText field with Html highlights. Available values will be populated from the FieldSettings.
*/
export const HtmlEditor: React.FC<ReactClientFieldSetting> = (props) => {
const theme = useTheme()

return <SnHtmlEditor theme={theme} {...props} />
}
1 change: 1 addition & 0 deletions apps/sensenet/src/components/field-controls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './rich-text-editor'
export * from './webhook-trigger'
export * from './webhook-headers'
export * from './webhook-payload'
export * from './html-editor'
2 changes: 2 additions & 0 deletions apps/sensenet/src/components/react-control-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export const reactControlMapper = (repository: Repository) => {
return FieldControls.WebhookHeaders
case 'sn:WebhookPayload':
return FieldControls.WebhookPayload
case 'sn:HtmlEditor':
return FieldControls.HtmlEditor
default:
}

Expand Down
2 changes: 1 addition & 1 deletion apps/sensenet/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports = {
APP_VERSION: require('./package.json').version,
}),
new MonacoWebpackPlugin({
languages: ['json', 'xml'],
languages: ['json', 'xml', 'html', 'javascript', 'markdown'],
features: [
'!accessibilityHelp',
'!anchorSelect',
Expand Down
10 changes: 10 additions & 0 deletions examples/sn-react-calendar/test/__mocks__/react-monaco-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react'
/**
* This is a mock MonacoEditor component for testing purposes.
* It simply renders an empty div.
*
* @returns {JSX.Element} An empty div.
*/
export default function MonacoEditor(): JSX.Element {
return <div />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react'
/**
* This is a mock MonacoEditor component for testing purposes.
* It simply renders an empty div.
*
* @returns {JSX.Element} An empty div.
*/
export default function MonacoEditor(): JSX.Element {
return <div />
}
2 changes: 1 addition & 1 deletion examples/sn-react-usersearch/test/user-search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { mount, shallow } from 'enzyme'
import React from 'react'
import { act } from 'react-dom/test-utils'
import UserSearchPanel from '../src/components/user-search'
import { TestUserList } from './_mocks_/test_contents'
import { TestUserList } from './__mocks__/test_contents'

describe('The user search component instance', () => {
let wrapper: any
Expand Down
10 changes: 10 additions & 0 deletions packages/sn-control-mapper/test/__mocks__/react-monaco-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react'
/**
* This is a mock MonacoEditor component for testing purposes.
* It simply renders an empty div.
*
* @returns {JSX.Element} An empty div.
*/
export default function MonacoEditor(): JSX.Element {
return <div />
}
101 changes: 101 additions & 0 deletions packages/sn-controls-react/src/fieldcontrols/html-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @module FieldControls
*/
import { InputLabel, Theme } from '@material-ui/core'
import React, { useEffect, useRef, useState } from 'react'
import MonacoEditor from 'react-monaco-editor'

import { changeTemplatedValue } from '../helpers'
import { ReactClientFieldSetting } from './client-field-setting'

/**
* Field control that represents a LongText field. Available values will be populated from the FieldSettings.
*/
export const HtmlEditor: React.FC<
ReactClientFieldSetting & {
theme?: Theme
setValue?: (value: string) => void
}
> = (props) => {
const initialState =
props.fieldValue || (props.actionName === 'new' && changeTemplatedValue(props.settings.DefaultValue)) || ''
const [value, setValue] = useState(initialState)
const readonly = props.actionName === 'browse' || props.settings.ReadOnly

const editorRef = useRef<MonacoEditor>(null)
const containerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (!editorRef.current) {
return
}
editorRef.current.editor!.onDidContentSizeChange(() => {
const contentHeight = editorRef?.current?.editor?.getContentHeight()

containerRef.current!.style.height = `${contentHeight}px`
})
}, [editorRef])

const editorChangeHandler = (newValue: string) => {
setValue(newValue)
props.fieldOnChange?.(props.settings.Name, newValue)
}

return (
<>
<InputLabel shrink>{props.settings.DisplayName}</InputLabel>

<div style={{ maxHeight: '68vh', margin: '0.5rem 0' }} ref={containerRef} data-test="html-editor-container">
<MonacoEditor
ref={editorRef}
{...props}
width="100%"
height="100%"
value={value}
onChange={editorChangeHandler}
options={{
automaticLayout: true,
contextmenu: true,
hideCursorInOverviewRuler: true,
lineNumbers: 'on',
selectOnLineNumbers: true,
scrollBeyondLastLine: false,
minimap: {
enabled: true,
},
roundedSelection: false,
readOnly: readonly,
cursorStyle: 'line',
scrollbar: {
horizontalSliderSize: 4,
verticalScrollbarSize: 6,
// vertical: 'hidden',
},

wordWrap: 'on',
autoIndent: 'advanced',
matchBrackets: 'always',
language: 'html',
suggest: {
snippetsPreventQuickSuggestions: false,
showProperties: true,
showKeywords: true,
showWords: true,
},
}}
theme={props.theme?.palette.type === 'dark' ? 'admin-ui-dark' : 'vs-light'}
editorWillMount={(monaco) => {
monaco.editor.defineTheme('admin-ui-dark', {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#121212',
},
})
}}
/>
</div>
</>
)
}
1 change: 1 addition & 0 deletions packages/sn-controls-react/src/fieldcontrols/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export * from './icon'
export * from './localization'
export * from './file-size'
export * from './page-count'
export * from './html-editor'
10 changes: 10 additions & 0 deletions packages/sn-controls-react/test/__mocks__/react-monaco-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react'
/**
* This is a mock MonacoEditor component for testing purposes.
* It simply renders an empty div.
*
* @returns {JSX.Element} An empty div.
*/
export default function MonacoEditor(): JSX.Element {
return <div />
}
56 changes: 56 additions & 0 deletions packages/sn-controls-react/test/html-editor.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ActionName } from '@sensenet/control-mapper'
import { mount } from 'enzyme'
import React from 'react'
import { act } from 'react-dom/test-utils'
import { defaultLocalization, HtmlEditor } from '../src/fieldcontrols'

jest.mock('react-monaco-editor', () =>
jest.fn((props) => {
console.log(props)
return (
<div data-test="mock-monaco-editor" onChange={props.fieldOnChange} ref={props.editorRef}>
{props.value}
</div>
)
}),
)

describe('Html Editor', () => {
it('should display the content', async () => {
const onChange = jest.fn()
const props = {
actionName: 'edit' as ActionName,
settings: {
Name: 'test',
DisplayName: 'Test',
Description: 'Test',
Compulsory: false,
ReadOnly: false,
DefaultValue: 'Test',
Type: 'LongTextField',
FieldClassName: 'SenseNet.ContentRepository.Fields.LongTextField',
},
localization: defaultLocalization,
fieldValue: '<p>Test</p>',
fieldOnChange: onChange,
}

const wrapper = mount(<HtmlEditor {...props} />)

wrapper.update()

const htmlEditorContainer = wrapper!.find('[data-test="html-editor-container"]')

expect(htmlEditorContainer.text()).toBe('<p>Test</p>')

const mockMonacoEditor = wrapper.find('[data-test="mock-monaco-editor"]')

await act(async () => {
mockMonacoEditor.prop('onChange')?.('<p>Changed Test</p>' as any)
})

//should be called with onChange

expect(props.fieldOnChange).toBeCalledWith('<p>Changed Test</p>')
})
})
2 changes: 1 addition & 1 deletion packages/sn-controls-react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{ "path": "../sn-editor-react" },
{ "path": "../sn-hooks-react" },
{ "path": "../sn-pickers-react" },
{ "path": "../sn-query"},
{ "path": "../sn-query" },
{ "path": "../sn-search-react" }
]
}

0 comments on commit e194979

Please sign in to comment.