Skip to content
This repository was archived by the owner on Oct 25, 2022. It is now read-only.

Commit 09f1c9c

Browse files
authored
Merge pull request #239 from eea/develop
Add poweruser menu (#234)
2 parents 030b6f2 + 9abd1c5 commit 09f1c9c

File tree

12 files changed

+289
-23
lines changed

12 files changed

+289
-23
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ All notable changes to this project will be documented in this file. Dates are d
44

55
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
66

7+
#### [6.0.0](https://github.com/eea/volto-slate/compare/5.4.1...6.0.0)
8+
9+
- Use block data form [`#240`](https://github.com/eea/volto-slate/pull/240)
10+
- Add poweruser menu [`#234`](https://github.com/eea/volto-slate/pull/234)
11+
- Release 6.0.0 [`de78cb5`](https://github.com/eea/volto-slate/commit/de78cb5495e0a8ea262ff45b05b87f900dccf5eb)
12+
713
#### [5.4.1](https://github.com/eea/volto-slate/compare/5.4.0...5.4.1)
814

9-
- chore(cypress): Fix paste html [`0bebd22`](https://github.com/eea/volto-slate/commit/0bebd222609ad8186d12be93c230ac1bc1b3c16d)
15+
> 19 March 2022
16+
17+
- chore(cypress): Fix paste html [`#237`](https://github.com/eea/volto-slate/pull/237)
1018

1119
#### [5.4.0](https://github.com/eea/volto-slate/compare/5.3.5...5.4.0)
1220

Jenkinsfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,10 @@ pipeline {
184184
unstash "xunit-reports"
185185
unstash "cypress-coverage"
186186
def scannerHome = tool 'SonarQubeScanner';
187-
def nodeJS = tool 'NodeJS11';
187+
def nodeJS = tool 'NodeJS';
188188
withSonarQubeEnv('Sonarqube') {
189189
sh '''sed -i "s#/opt/frontend/my-volto-project/src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info'''
190-
sh "export PATH=$PATH:${scannerHome}/bin:${nodeJS}/bin; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info,./cypress-coverage/coverage/lcov.info -Dsonar.sources=./src -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
190+
sh "export PATH=${scannerHome}/bin:${nodeJS}/bin:$PATH; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info,./cypress-coverage/coverage/lcov.info -Dsonar.sources=./src -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
191191
sh '''try=2; while [ \$try -gt 0 ]; do curl -s -XPOST -u "${SONAR_AUTH_TOKEN}:" "${SONAR_HOST_URL}api/project_tags/set?project=${GIT_NAME}-${BRANCH_NAME}&tags=${SONARQUBE_TAGS},${BRANCH_NAME}" > set_tags_result; if [ \$(grep -ic error set_tags_result ) -eq 0 ]; then try=0; else cat set_tags_result; echo "... Will retry"; sleep 60; try=\$(( \$try - 1 )); fi; done'''
192192
}
193193
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "volto-slate",
3-
"version": "5.4.1",
3+
"version": "6.0.0",
44
"description": "Slate.js integration with Volto",
55
"main": "src/index.js",
66
"author": "European Environment Agency: IDM2 A-Team",

src/blocks/Text/DefaultTextBlockEditor.jsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Dimmer, Loader, Message, Segment } from 'semantic-ui-react';
99
import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
1010
import config from '@plone/volto/registry';
1111
import {
12-
InlineForm,
12+
BlockDataForm,
1313
SidebarPortal,
1414
BlockChooserButton,
1515
} from '@plone/volto/components';
@@ -23,6 +23,7 @@ import {
2323
} from 'volto-slate/utils';
2424
import { Transforms } from 'slate';
2525

26+
import PersistentSlashMenu from './SlashMenu';
2627
import ShortcutListing from './ShortcutListing';
2728
import MarkdownIntroduction from './MarkdownIntroduction';
2829
import { handleKey } from './keyboard';
@@ -86,6 +87,17 @@ export const DefaultTextBlockEditor = (props) => {
8687
[props],
8788
);
8889

90+
const slateSettings = React.useMemo(
91+
() => ({
92+
...config.settings.slate,
93+
persistentHelpers: [
94+
...config.settings.slate.persistentHelpers,
95+
PersistentSlashMenu,
96+
],
97+
}),
98+
[],
99+
);
100+
89101
const onDrop = React.useCallback(
90102
(files) => {
91103
// TODO: need to fix setUploading, treat uploading indicator
@@ -231,6 +243,7 @@ export const DefaultTextBlockEditor = (props) => {
231243
onKeyDown={handleKey}
232244
selected={selected}
233245
placeholder={placeholder}
246+
slateSettings={slateSettings}
234247
/>
235248
{DEBUG ? <div>{block}</div> : ''}
236249
</>
@@ -264,7 +277,7 @@ export const DefaultTextBlockEditor = (props) => {
264277
<>
265278
<ShortcutListing />
266279
<MarkdownIntroduction />
267-
<InlineForm
280+
<BlockDataForm
268281
schema={schema}
269282
title={schema.title}
270283
onChangeField={(id, value) => {
@@ -274,6 +287,7 @@ export const DefaultTextBlockEditor = (props) => {
274287
});
275288
}}
276289
formData={data}
290+
block={block}
277291
/>
278292
</>
279293
)}

src/blocks/Text/ShortcutListing.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const ShortcutListing = (props) => {
1212

1313
<Segment secondary attached>
1414
<List>
15+
<List.Item>
16+
Type a slash (<em>/</em>) to change block type
17+
</List.Item>
1518
{Object.entries(hotkeys || {}).map(([shortcut, { format, type }]) => (
1619
<List.Item key={shortcut}>{`${shortcut}: ${format}`}</List.Item>
1720
))}

src/blocks/Text/SlashMenu.jsx

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { filter, isEmpty } from 'lodash';
4+
import { Menu } from 'semantic-ui-react';
5+
import { useIntl, FormattedMessage } from 'react-intl';
6+
import { Icon } from '@plone/volto/components';
7+
8+
const emptySlateBlock = () => ({
9+
value: [
10+
{
11+
children: [
12+
{
13+
text: '',
14+
},
15+
],
16+
type: 'p',
17+
},
18+
],
19+
plaintext: '',
20+
});
21+
22+
const useIsMounted = () => {
23+
const ref = React.useRef();
24+
React.useEffect(() => {
25+
ref.current = true;
26+
return () => (ref.current = false);
27+
}, []);
28+
return ref.current;
29+
};
30+
31+
const SlashMenu = ({
32+
currentBlock,
33+
onMutateBlock,
34+
selected,
35+
availableBlocks,
36+
}) => {
37+
const intl = useIntl();
38+
39+
return (
40+
<div className="power-user-menu">
41+
<Menu vertical fluid borderless>
42+
{availableBlocks.map((block, index) => (
43+
<Menu.Item
44+
key={block.id}
45+
className={block.id}
46+
active={index === selected}
47+
onClick={(e) => {
48+
// onInsertBlock(currentBlock, { '@type': block.id });
49+
onMutateBlock(currentBlock, { '@type': block.id });
50+
e.stopPropagation();
51+
}}
52+
>
53+
<Icon name={block.icon} size="24px" />
54+
{intl.formatMessage({
55+
id: block.title,
56+
defaultMessage: block.title,
57+
})}
58+
</Menu.Item>
59+
))}
60+
{availableBlocks.length === 0 && (
61+
<Menu.Item>
62+
<FormattedMessage
63+
id="No matching blocks"
64+
defaultMessage="No matching blocks"
65+
/>
66+
</Menu.Item>
67+
)}
68+
</Menu>
69+
</div>
70+
);
71+
};
72+
73+
SlashMenu.propTypes = {
74+
currentBlock: PropTypes.string.isRequired,
75+
onInsertBlock: PropTypes.func,
76+
selected: PropTypes.number,
77+
blocksConfig: PropTypes.arrayOf(PropTypes.any),
78+
};
79+
80+
/**
81+
* A SlashMenu wrapper implemented as a volto-slate PersistentHelper.
82+
*/
83+
const PersistentSlashMenu = ({ editor }) => {
84+
const props = editor.getBlockProps();
85+
const {
86+
block,
87+
blocksConfig,
88+
data,
89+
onMutateBlock,
90+
properties,
91+
selected,
92+
allowedBlocks,
93+
detached,
94+
} = props;
95+
const disableNewBlocks = data?.disableNewBlocks || detached;
96+
97+
const [slashMenuSelected, setSlashMenuSelected] = React.useState(0);
98+
99+
const useAllowedBlocks = !isEmpty(allowedBlocks);
100+
const slashCommand = data.plaintext?.trim().match(/^\/([a-z]*)$/);
101+
102+
const availableBlocks = React.useMemo(
103+
() =>
104+
filter(blocksConfig, (item) =>
105+
useAllowedBlocks
106+
? allowedBlocks.includes(item.id)
107+
: typeof item.restricted === 'function'
108+
? !item.restricted({ properties, block: item })
109+
: !item.restricted,
110+
)
111+
.filter(
112+
// TODO: make it work with intl?
113+
(block) => slashCommand && block.id.indexOf(slashCommand[1]) === 0,
114+
)
115+
.sort((a, b) => (a.title < b.title ? -1 : 1)),
116+
[allowedBlocks, blocksConfig, properties, slashCommand, useAllowedBlocks],
117+
);
118+
119+
const slashMenuSize = availableBlocks.length;
120+
const show = selected && slashCommand && !disableNewBlocks;
121+
122+
const isMounted = useIsMounted();
123+
124+
React.useEffect(() => {
125+
if (isMounted && show && slashMenuSelected > slashMenuSize - 1) {
126+
setSlashMenuSelected(slashMenuSize - 1);
127+
}
128+
}, [show, slashMenuSelected, isMounted, slashMenuSize]);
129+
130+
editor.showSlashMenu = show;
131+
132+
editor.slashEnter = () =>
133+
slashMenuSize > 0 &&
134+
onMutateBlock(
135+
block,
136+
{
137+
'@type': availableBlocks[slashMenuSelected].id,
138+
},
139+
emptySlateBlock(),
140+
);
141+
142+
editor.slashArrowUp = () =>
143+
setSlashMenuSelected(
144+
slashMenuSelected === 0 ? slashMenuSize - 1 : slashMenuSelected - 1,
145+
);
146+
147+
editor.slashArrowDown = () =>
148+
setSlashMenuSelected(
149+
slashMenuSelected >= slashMenuSize - 1 ? 0 : slashMenuSelected + 1,
150+
);
151+
152+
return show ? (
153+
<SlashMenu
154+
currentBlock={block}
155+
onMutateBlock={onMutateBlock}
156+
availableBlocks={availableBlocks}
157+
selected={slashMenuSelected}
158+
/>
159+
) : (
160+
''
161+
);
162+
};
163+
164+
export default PersistentSlashMenu;

src/blocks/Text/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
moveListItemUp,
1717
traverseBlocks,
1818
unwrapEmptyString,
19+
slashMenu,
20+
cancelEsc,
1921
} from './keyboard';
2022
import { withDeleteSelectionOnEnter } from 'volto-slate/editor/extensions';
2123
import {
@@ -58,21 +60,25 @@ export default (config) => {
5860
joinWithNextBlock, // Delete at end of block joins with next block
5961
],
6062
Enter: [
63+
slashMenu,
6164
unwrapEmptyString,
6265
softBreak, // Handles shift+Enter as a newline (<br/>)
6366
],
6467
ArrowUp: [
68+
slashMenu,
6569
moveListItemUp, // Move up a list with with Ctrl+up
6670
goUp, // Select previous block
6771
],
6872
ArrowDown: [
73+
slashMenu,
6974
moveListItemDown, // Move down a list item with Ctrl+down
7075
goDown, // Select next block
7176
],
7277
Tab: [
7378
indentListItems, // <tab> and <c-tab> behaviour for list items
7479
traverseBlocks,
7580
],
81+
Escape: [cancelEsc],
7682
},
7783
textblockDetachedKeyboardHandlers: {
7884
Enter: [

src/blocks/Text/keyboard/cancelEsc.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const cancelEsc = ({ editor, event }) => {
2+
// TODO: this doesn't work, escape canceling doesn't work.
3+
event.stopPropagation();
4+
event.nativeEvent.stopImmediatePropagation();
5+
event.preventDefault();
6+
return true;
7+
};

src/blocks/Text/keyboard/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export * from './moveListItems';
88
export * from './softBreak';
99
export * from './traverseBlocks';
1010
export * from './unwrapEmptyString';
11+
export * from './slashMenu';
12+
export * from './cancelEsc';
1113

1214
/**
1315
* Takes all the handlers from `slate.textblockKeyboardHandlers` that are

src/blocks/Text/keyboard/slashMenu.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const slashMenu = ({ editor, event }) => {
2+
if (!editor.showSlashMenu) return;
3+
4+
const { slashArrowUp, slashArrowDown, slashEnter } = editor;
5+
6+
const handlers = {
7+
ArrowUp: slashArrowUp,
8+
ArrowDown: slashArrowDown,
9+
Enter: slashEnter,
10+
};
11+
12+
const handler = handlers[event.key];
13+
if (handler) handler();
14+
15+
return true;
16+
};

0 commit comments

Comments
 (0)