@plone/volto 17.0.0-alpha.13 → 17.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +68 -0
- package/README.md +2 -2
- package/docker-compose.yml +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +5 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +5 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +5 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +49 -44
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +5 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +5 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +5 -0
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +5 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +5 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +5 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +5 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +5 -0
- package/locales/ro.json +1 -1
- package/locales/volto.pot +5 -0
- package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +2 -1
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/actions/index.js +1 -1
- package/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +20 -16
- package/packages/volto-slate/src/blocks/Text/index.js +2 -2
- package/packages/volto-slate/src/editor/config.jsx +5 -4
- package/packages/volto-slate/src/editor/index.js +4 -4
- package/packages/volto-slate/src/editor/less/slate.less +28 -0
- package/packages/volto-slate/src/editor/render.jsx +68 -8
- package/packages/volto-slate/src/editor/ui/SlateContextToolbar.jsx +2 -2
- package/packages/volto-slate/src/editor/ui/index.js +15 -15
- package/packages/volto-slate/src/index.js +2 -2
- package/src/components/manage/AnchorPlugin/index.jsx +2 -2
- package/src/components/manage/AnchorPlugin/utils/EditorUtils.js +3 -1
- package/src/components/manage/Blocks/Block/Style.jsx +2 -2
- package/src/components/manage/Blocks/Listing/DefaultTemplate.jsx +18 -3
- package/src/components/manage/Blocks/Listing/ListingBody.jsx +30 -8
- package/src/components/manage/Blocks/Listing/getAsyncData.js +3 -5
- package/src/components/manage/Blocks/Search/components/index.js +13 -13
- package/src/components/manage/Blocks/Search/hocs/index.js +2 -2
- package/src/components/manage/Blocks/Search/hocs/withQueryString.jsx +2 -2
- package/src/components/manage/Blocks/Title/View.jsx +15 -5
- package/src/components/manage/Blocks/Title/View.test.jsx +16 -1
- package/src/components/manage/Blocks/ToC/View.jsx +8 -1
- package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +17 -4
- package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +6 -2
- package/src/components/manage/Blocks/ToC/variations/index.js +3 -1
- package/src/components/theme/Anontools/Anontools.jsx +45 -72
- package/src/components/theme/Anontools/Anontools.stories.jsx +16 -6
- package/src/components/theme/Anontools/Anontools.test.jsx +16 -2
- package/src/config/RichTextEditor/Blocks.jsx +2 -2
- package/src/config/RichTextEditor/FromHTML.jsx +2 -2
- package/src/config/RichTextEditor/Styles.jsx +1 -1
- package/src/constants/Indexes.js +3 -1
- package/src/express-middleware/devproxy.js +1 -1
- package/src/express-middleware/files.js +3 -3
- package/src/express-middleware/images.js +4 -4
- package/src/express-middleware/robotstxt.js +1 -1
- package/src/express-middleware/sitemap.js +1 -1
- package/src/express-middleware/static.js +3 -3
- package/src/helpers/Extensions/index.js +2 -1
- package/src/helpers/MessageLabels/MessageLabels.js +4 -0
- package/src/helpers/ScrollToTop/ScrollToTop.jsx +5 -3
- package/src/helpers/Utils/UseDetectClickOutside.stories.jsx +191 -0
- package/src/helpers/index.js +9 -10
- package/src/hooks/clipboard/useClipboard.js +26 -0
- package/src/hooks/content/useContent.js +31 -0
- package/src/hooks/index.js +2 -0
- package/src/middleware/index.js +2 -2
- package/src/start-server.js +2 -2
- package/theme/themes/pastanaga/extras/blocks.less +3 -1
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
serializeNodes,
|
|
3
|
+
serializeNodesToText,
|
|
4
|
+
} from '@plone/volto-slate/editor/render';
|
|
2
5
|
import config from '@plone/volto/registry';
|
|
6
|
+
import { isEqual } from 'lodash';
|
|
7
|
+
import Slugger from 'github-slugger';
|
|
3
8
|
|
|
4
9
|
const TextBlockView = (props) => {
|
|
5
10
|
const { id, data, styling = {} } = props;
|
|
6
11
|
const { value, override_toc } = data;
|
|
7
12
|
const metadata = props.metadata || props.properties;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
res.id = id;
|
|
18
|
-
}
|
|
13
|
+
const { topLevelTargetElements } = config.settings.slate;
|
|
14
|
+
|
|
15
|
+
const getAttributes = (node, path) => {
|
|
16
|
+
const res = { ...styling };
|
|
17
|
+
if (node.type && isEqual(path, [0])) {
|
|
18
|
+
if (topLevelTargetElements.includes(node.type) || override_toc) {
|
|
19
|
+
const text = serializeNodesToText(node?.children || []);
|
|
20
|
+
const slug = Slugger.slug(text);
|
|
21
|
+
res.id = slug || id;
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
}
|
|
24
|
+
return res;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return serializeNodes(value, getAttributes, { metadata: metadata });
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
export default TextBlockView;
|
|
@@ -35,7 +35,7 @@ import textSVG from '@plone/volto/icons/subtext.svg';
|
|
|
35
35
|
|
|
36
36
|
export { TextBlockView, TextBlockEdit, TextBlockSchema };
|
|
37
37
|
|
|
38
|
-
export default (config)
|
|
38
|
+
export default function applyConfig(config) {
|
|
39
39
|
config.settings.slate = {
|
|
40
40
|
// TODO: should we inverse order? First here gets executed last
|
|
41
41
|
textblockExtensions: [
|
|
@@ -165,4 +165,4 @@ export default (config) => {
|
|
|
165
165
|
restricted: true,
|
|
166
166
|
};
|
|
167
167
|
return config;
|
|
168
|
-
}
|
|
168
|
+
}
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
bTagDeserializer,
|
|
44
44
|
codeTagDeserializer,
|
|
45
45
|
} from './deserialize';
|
|
46
|
+
import { renderLinkElement } from './render';
|
|
46
47
|
|
|
47
48
|
// Registry of available buttons
|
|
48
49
|
export const buttons = {
|
|
@@ -234,10 +235,10 @@ export const defaultBlockType = 'p';
|
|
|
234
235
|
export const elements = {
|
|
235
236
|
default: ({ attributes, children }) => <p {...attributes}>{children}</p>,
|
|
236
237
|
|
|
237
|
-
h1: (
|
|
238
|
-
h2: (
|
|
239
|
-
h3: (
|
|
240
|
-
h4: (
|
|
238
|
+
h1: renderLinkElement('h1'),
|
|
239
|
+
h2: renderLinkElement('h2'),
|
|
240
|
+
h3: renderLinkElement('h3'),
|
|
241
|
+
h4: renderLinkElement('h4'),
|
|
241
242
|
|
|
242
243
|
li: ({ attributes, children }) => <li {...attributes}>{children}</li>,
|
|
243
244
|
ol: ({ attributes, children }) => <ol {...attributes}>{children}</ol>,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as slateConfig from './config';
|
|
2
2
|
import installDefaultPlugins from './plugins';
|
|
3
|
-
export SlateEditor from './SlateEditor';
|
|
4
|
-
export EditorReference from './EditorReference';
|
|
3
|
+
export { default as SlateEditor } from './SlateEditor';
|
|
4
|
+
export { default as EditorReference } from './EditorReference';
|
|
5
5
|
|
|
6
|
-
export default (config)
|
|
6
|
+
export default function applyConfig(config) {
|
|
7
7
|
config.settings.slate = {
|
|
8
8
|
...slateConfig,
|
|
9
9
|
// showExpandedToolbar: false,
|
|
@@ -11,4 +11,4 @@ export default (config) => {
|
|
|
11
11
|
};
|
|
12
12
|
config = installDefaultPlugins(config);
|
|
13
13
|
return config;
|
|
14
|
-
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
h1,
|
|
2
|
+
h2,
|
|
3
|
+
h3,
|
|
4
|
+
h4 {
|
|
5
|
+
&:hover {
|
|
6
|
+
a.anchor {
|
|
7
|
+
svg {
|
|
8
|
+
opacity: 1;
|
|
9
|
+
transform: rotate(15deg);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
a.anchor {
|
|
15
|
+
position: absolute;
|
|
16
|
+
display: inline-block;
|
|
17
|
+
margin-left: 5px;
|
|
18
|
+
vertical-align: middle;
|
|
19
|
+
|
|
20
|
+
svg {
|
|
21
|
+
width: 1.6ch;
|
|
22
|
+
fill: #42526e;
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transform: rotate(15deg) translate(-8px, 2px);
|
|
25
|
+
transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import { toast } from 'react-toastify';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
3
6
|
import { Node, Text } from 'slate';
|
|
4
7
|
import cx from 'classnames';
|
|
5
|
-
import { isEmpty,
|
|
8
|
+
import { isEmpty, omit } from 'lodash';
|
|
9
|
+
import { UniversalLink, Toast } from '@plone/volto/components';
|
|
10
|
+
import { messages, addAppURL } from '@plone/volto/helpers';
|
|
11
|
+
import useClipboard from '@plone/volto/hooks/clipboard/useClipboard';
|
|
6
12
|
import config from '@plone/volto/registry';
|
|
13
|
+
import linkSVG from '@plone/volto/icons/link.svg';
|
|
14
|
+
|
|
15
|
+
import './less/slate.less';
|
|
7
16
|
|
|
8
17
|
const OMITTED = ['editor', 'path'];
|
|
9
18
|
|
|
@@ -106,13 +115,7 @@ export const serializeNodes = (nodes, getAttributes, extras = {}) => {
|
|
|
106
115
|
mode="view"
|
|
107
116
|
key={path}
|
|
108
117
|
data-slate-data={node.data ? serializeData(node) : null}
|
|
109
|
-
attributes={
|
|
110
|
-
isEqual(path, [0])
|
|
111
|
-
? getAttributes
|
|
112
|
-
? getAttributes(node, path)
|
|
113
|
-
: null
|
|
114
|
-
: null
|
|
115
|
-
}
|
|
118
|
+
attributes={getAttributes ? getAttributes(node, path) : null}
|
|
116
119
|
extras={extras}
|
|
117
120
|
>
|
|
118
121
|
{_serializeNodes(Array.from(Node.children(editor, path)))}
|
|
@@ -153,3 +156,60 @@ export const serializeNodesToText = (nodes) => {
|
|
|
153
156
|
|
|
154
157
|
export const serializeNodesToHtml = (nodes) =>
|
|
155
158
|
renderToStaticMarkup(serializeNodes(nodes));
|
|
159
|
+
|
|
160
|
+
export const renderLinkElement = (tagName) => {
|
|
161
|
+
function LinkElement({
|
|
162
|
+
attributes,
|
|
163
|
+
children,
|
|
164
|
+
mode = 'edit',
|
|
165
|
+
className = null,
|
|
166
|
+
}) {
|
|
167
|
+
const { slate = {} } = config.settings;
|
|
168
|
+
const Tag = tagName;
|
|
169
|
+
const slug = attributes.id || '';
|
|
170
|
+
const location = useLocation();
|
|
171
|
+
const appPathname = addAppURL(location.pathname);
|
|
172
|
+
// eslint-disable-next-line no-unused-vars
|
|
173
|
+
const [copied, copy, setCopied] = useClipboard(
|
|
174
|
+
appPathname.concat(`#${slug}`),
|
|
175
|
+
);
|
|
176
|
+
const intl = useIntl();
|
|
177
|
+
|
|
178
|
+
return slate.useLinkedHeadings === false ? (
|
|
179
|
+
<Tag {...attributes} className={className}>
|
|
180
|
+
{children}
|
|
181
|
+
</Tag>
|
|
182
|
+
) : (
|
|
183
|
+
<Tag {...attributes} className={className}>
|
|
184
|
+
{children}
|
|
185
|
+
{mode === 'view' && slug && (
|
|
186
|
+
<UniversalLink
|
|
187
|
+
className="anchor"
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
tabIndex={-1}
|
|
190
|
+
href={`#${slug}`}
|
|
191
|
+
>
|
|
192
|
+
<svg
|
|
193
|
+
{...linkSVG.attributes}
|
|
194
|
+
dangerouslySetInnerHTML={{ __html: linkSVG.content }}
|
|
195
|
+
height={null}
|
|
196
|
+
onClick={() => {
|
|
197
|
+
copy();
|
|
198
|
+
|
|
199
|
+
toast.info(
|
|
200
|
+
<Toast
|
|
201
|
+
info
|
|
202
|
+
title={intl.formatMessage(messages.success)}
|
|
203
|
+
content={intl.formatMessage(messages.urlClipboardCopy)}
|
|
204
|
+
/>,
|
|
205
|
+
);
|
|
206
|
+
}}
|
|
207
|
+
></svg>
|
|
208
|
+
</UniversalLink>
|
|
209
|
+
)}
|
|
210
|
+
</Tag>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
LinkElement.displayName = `${tagName}LinkElement`;
|
|
214
|
+
return LinkElement;
|
|
215
|
+
};
|
|
@@ -3,11 +3,11 @@ import Toolbar from './Toolbar';
|
|
|
3
3
|
|
|
4
4
|
// A toolbar that conditionally renders itself based on the presense of
|
|
5
5
|
// children
|
|
6
|
-
export default ({ editor, plugins, show })
|
|
6
|
+
export default function SlateContextToolbar({ editor, plugins, show }) {
|
|
7
7
|
if (!show) {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const components = plugins.map((plug) => plug(editor)).filter((c) => !!c);
|
|
12
12
|
return components.length ? <Toolbar>{components}</Toolbar> : '';
|
|
13
|
-
}
|
|
13
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export BasicToolbar from './BasicToolbar';
|
|
2
|
-
export BlockButton from './BlockButton';
|
|
3
|
-
export ClearFormattingButton from './ClearFormattingButton';
|
|
4
|
-
export ExpandedToolbar from './ExpandedToolbar';
|
|
5
|
-
export Expando from './Expando';
|
|
6
|
-
export MarkButton from './MarkButton';
|
|
7
|
-
export Menu from './Menu';
|
|
8
|
-
export Separator from './Separator';
|
|
9
|
-
export SlateContextToolbar from './SlateContextToolbar';
|
|
10
|
-
export SlateToolbar from './SlateToolbar';
|
|
11
|
-
export Toolbar from './Toolbar';
|
|
12
|
-
export ToolbarButton from './ToolbarButton';
|
|
13
|
-
export MarkElementButton from './MarkElementButton';
|
|
14
|
-
export PositionedToolbar from './PositionedToolbar';
|
|
15
|
-
export InlineToolbar from './InlineToolbar';
|
|
1
|
+
export { default as BasicToolbar } from './BasicToolbar';
|
|
2
|
+
export { default as BlockButton } from './BlockButton';
|
|
3
|
+
export { default as ClearFormattingButton } from './ClearFormattingButton';
|
|
4
|
+
export { default as ExpandedToolbar } from './ExpandedToolbar';
|
|
5
|
+
export { default as Expando } from './Expando';
|
|
6
|
+
export { default as MarkButton } from './MarkButton';
|
|
7
|
+
export { default as Menu } from './Menu';
|
|
8
|
+
export { default as Separator } from './Separator';
|
|
9
|
+
export { default as SlateContextToolbar } from './SlateContextToolbar';
|
|
10
|
+
export { default as SlateToolbar } from './SlateToolbar';
|
|
11
|
+
export { default as Toolbar } from './Toolbar';
|
|
12
|
+
export { default as ToolbarButton } from './ToolbarButton';
|
|
13
|
+
export { default as MarkElementButton } from './MarkElementButton';
|
|
14
|
+
export { default as PositionedToolbar } from './PositionedToolbar';
|
|
15
|
+
export { default as InlineToolbar } from './InlineToolbar';
|
|
@@ -8,7 +8,7 @@ import RichTextWidgetView from './widgets/RichTextWidgetView';
|
|
|
8
8
|
import HtmlSlateWidget from './widgets/HtmlSlateWidget';
|
|
9
9
|
import ObjectByTypeWidget from './widgets/ObjectByTypeWidget';
|
|
10
10
|
|
|
11
|
-
export default (config)
|
|
11
|
+
export default function applyConfig(config) {
|
|
12
12
|
config = [installSlate, installTextBlock, installTableBlock].reduce(
|
|
13
13
|
(acc, apply) => apply(acc),
|
|
14
14
|
config,
|
|
@@ -58,4 +58,4 @@ export default (config) => {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return config;
|
|
61
|
-
}
|
|
61
|
+
}
|
|
@@ -44,7 +44,7 @@ function unboundRemoveEntity(editorState) {
|
|
|
44
44
|
return newEditorState;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export default (config = {})
|
|
47
|
+
export default function AnchorPlugin(config = {}) {
|
|
48
48
|
// ToDo: Get rif of the remainings of having the original CSS modules
|
|
49
49
|
const defaultTheme = {};
|
|
50
50
|
|
|
@@ -79,4 +79,4 @@ export default (config = {}) => {
|
|
|
79
79
|
setEditorState(removeEntity(getEditorState())),
|
|
80
80
|
}),
|
|
81
81
|
};
|
|
82
|
-
}
|
|
82
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const EditorUtils = ({ draftJs }) => ({
|
|
2
2
|
createLinkAtSelection(editorState, url) {
|
|
3
3
|
const contentState = editorState
|
|
4
4
|
.getCurrentContent()
|
|
@@ -43,3 +43,5 @@ export default ({ draftJs }) => ({
|
|
|
43
43
|
return entity && entity.getType() === entityType;
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
export default EditorUtils;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
|
|
4
|
-
export default ({ data, detached, children })
|
|
4
|
+
export default function Style({ data, detached, children }) {
|
|
5
5
|
return (
|
|
6
6
|
<div
|
|
7
7
|
className={cx(
|
|
@@ -25,4 +25,4 @@ export default ({ data, detached, children }) => {
|
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
);
|
|
28
|
-
}
|
|
28
|
+
}
|
|
@@ -2,10 +2,15 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { ConditionalLink, UniversalLink } from '@plone/volto/components';
|
|
4
4
|
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
5
|
-
|
|
6
5
|
import { isInternalURL } from '@plone/volto/helpers/Url/Url';
|
|
7
6
|
|
|
8
|
-
const DefaultTemplate = ({
|
|
7
|
+
const DefaultTemplate = ({
|
|
8
|
+
headlineTag,
|
|
9
|
+
items,
|
|
10
|
+
linkTitle,
|
|
11
|
+
linkHref,
|
|
12
|
+
isEditMode,
|
|
13
|
+
}) => {
|
|
9
14
|
let link = null;
|
|
10
15
|
let href = linkHref?.[0]?.['@id'] || '';
|
|
11
16
|
|
|
@@ -19,6 +24,16 @@ const DefaultTemplate = ({ items, linkTitle, linkHref, isEditMode }) => {
|
|
|
19
24
|
link = <UniversalLink href={href}>{linkTitle || href}</UniversalLink>;
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
const getTitleTag = (tag) => {
|
|
28
|
+
const level = tag.slice(-1);
|
|
29
|
+
if (/\d/.test(level)) {
|
|
30
|
+
return `h${Number(level) + 1}`;
|
|
31
|
+
} else {
|
|
32
|
+
return 'h3';
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const TitleTag = headlineTag ? getTitleTag(headlineTag) : 'h3';
|
|
36
|
+
|
|
22
37
|
return (
|
|
23
38
|
<>
|
|
24
39
|
<div className="items">
|
|
@@ -26,7 +41,7 @@ const DefaultTemplate = ({ items, linkTitle, linkHref, isEditMode }) => {
|
|
|
26
41
|
<div className="listing-item" key={item['@id']}>
|
|
27
42
|
<ConditionalLink item={item} condition={!isEditMode}>
|
|
28
43
|
<div className="listing-body">
|
|
29
|
-
<
|
|
44
|
+
<TitleTag>{item.title ? item.title : item.id}</TitleTag>
|
|
30
45
|
<p>{item.description}</p>
|
|
31
46
|
</div>
|
|
32
47
|
</ConditionalLink>
|
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
import React, { createRef } from 'react';
|
|
1
|
+
import React, { createRef, useMemo } from 'react';
|
|
2
2
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import { Pagination, Dimmer, Loader } from 'semantic-ui-react';
|
|
5
|
+
import Slugger from 'github-slugger';
|
|
5
6
|
import { Icon } from '@plone/volto/components';
|
|
7
|
+
import { renderLinkElement } from '@plone/volto-slate/editor/render';
|
|
6
8
|
import config from '@plone/volto/registry';
|
|
7
9
|
import withQuerystringResults from './withQuerystringResults';
|
|
8
10
|
|
|
9
11
|
import paginationLeftSVG from '@plone/volto/icons/left-key.svg';
|
|
10
12
|
import paginationRightSVG from '@plone/volto/icons/right-key.svg';
|
|
11
13
|
|
|
14
|
+
const Headline = ({ headlineTag, id, data = {}, listingItems, isEditMode }) => {
|
|
15
|
+
let attr = { id };
|
|
16
|
+
const slug = Slugger.slug(data.headline);
|
|
17
|
+
attr.id = slug || id;
|
|
18
|
+
const LinkedHeadline = useMemo(() => renderLinkElement(headlineTag), [
|
|
19
|
+
headlineTag,
|
|
20
|
+
]);
|
|
21
|
+
return (
|
|
22
|
+
<LinkedHeadline
|
|
23
|
+
mode={!isEditMode && 'view'}
|
|
24
|
+
children={data.headline}
|
|
25
|
+
attributes={attr}
|
|
26
|
+
className={cx('headline', {
|
|
27
|
+
emptyListing: !listingItems?.length > 0,
|
|
28
|
+
})}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
12
33
|
const ListingBody = withQuerystringResults((props) => {
|
|
13
34
|
const {
|
|
14
35
|
data = {},
|
|
@@ -22,6 +43,7 @@ const ListingBody = withQuerystringResults((props) => {
|
|
|
22
43
|
nextBatch,
|
|
23
44
|
isFolderContentsListing,
|
|
24
45
|
hasLoaded,
|
|
46
|
+
id,
|
|
25
47
|
} = props;
|
|
26
48
|
|
|
27
49
|
let ListingBodyTemplate;
|
|
@@ -50,13 +72,13 @@ const ListingBody = withQuerystringResults((props) => {
|
|
|
50
72
|
return (
|
|
51
73
|
<>
|
|
52
74
|
{data.headline && (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
75
|
+
<Headline
|
|
76
|
+
headlineTag={HeadlineTag}
|
|
77
|
+
id={id}
|
|
78
|
+
listingItems={listingItems}
|
|
79
|
+
data={data}
|
|
80
|
+
isEditMode={isEditMode}
|
|
81
|
+
/>
|
|
60
82
|
)}
|
|
61
83
|
{listingItems?.length > 0 ? (
|
|
62
84
|
<div ref={listingRef}>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { getQueryStringResults } from '@plone/volto/actions';
|
|
2
2
|
import { resolveBlockExtensions } from '@plone/volto/helpers';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
export default function getListingBlockAsyncData({
|
|
5
5
|
dispatch,
|
|
6
6
|
id,
|
|
7
7
|
data,
|
|
8
8
|
path,
|
|
9
9
|
blocksConfig,
|
|
10
|
-
})
|
|
10
|
+
}) {
|
|
11
11
|
const { resolvedExtensions } = resolveBlockExtensions(data, blocksConfig);
|
|
12
12
|
|
|
13
13
|
return [
|
|
@@ -24,6 +24,4 @@ const getListingBlockAsyncData = ({
|
|
|
24
24
|
),
|
|
25
25
|
),
|
|
26
26
|
];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default getListingBlockAsyncData;
|
|
27
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export SearchDetails from './SearchDetails';
|
|
2
|
-
export Facets from './Facets';
|
|
3
|
-
export SelectFacet from './SelectFacet';
|
|
4
|
-
export CheckboxFacet from './CheckboxFacet';
|
|
5
|
-
export DateRangeFacet from './DateRangeFacet';
|
|
6
|
-
export SearchInput from './SearchInput';
|
|
7
|
-
export FilterList from './FilterList';
|
|
8
|
-
export SortOn from './SortOn';
|
|
9
|
-
export ToggleFacet from './ToggleFacet';
|
|
10
|
-
export SelectFacetFilterListEntry from './SelectFacetFilterListEntry';
|
|
11
|
-
export ToggleFacetFilterListEntry from './ToggleFacetFilterListEntry';
|
|
12
|
-
export DateRangeFacetFilterListEntry from './DateRangeFacetFilterListEntry';
|
|
13
|
-
export ViewSwitcher from './ViewSwitcher';
|
|
1
|
+
export { default as SearchDetails } from './SearchDetails';
|
|
2
|
+
export { default as Facets } from './Facets';
|
|
3
|
+
export { default as SelectFacet } from './SelectFacet';
|
|
4
|
+
export { default as CheckboxFacet } from './CheckboxFacet';
|
|
5
|
+
export { default as DateRangeFacet } from './DateRangeFacet';
|
|
6
|
+
export { default as SearchInput } from './SearchInput';
|
|
7
|
+
export { default as FilterList } from './FilterList';
|
|
8
|
+
export { default as SortOn } from './SortOn';
|
|
9
|
+
export { default as ToggleFacet } from './ToggleFacet';
|
|
10
|
+
export { default as SelectFacetFilterListEntry } from './SelectFacetFilterListEntry';
|
|
11
|
+
export { default as ToggleFacetFilterListEntry } from './ToggleFacetFilterListEntry';
|
|
12
|
+
export { default as DateRangeFacetFilterListEntry } from './DateRangeFacetFilterListEntry';
|
|
13
|
+
export { default as ViewSwitcher } from './ViewSwitcher';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export withQueryString from './withQueryString';
|
|
2
|
-
export withSearch from './withSearch';
|
|
1
|
+
export { default as withQueryString } from './withQueryString';
|
|
2
|
+
export { default as withSearch } from './withSearch';
|
|
@@ -10,7 +10,7 @@ function getDisplayName(WrappedComponent) {
|
|
|
10
10
|
* A HOC that injects querystring metadata information from the backend.
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
|
-
export default (WrappedComponent)
|
|
13
|
+
export default function withQueryString(WrappedComponent) {
|
|
14
14
|
function WithQueryString(props) {
|
|
15
15
|
const dispatch = useDispatch();
|
|
16
16
|
|
|
@@ -29,4 +29,4 @@ export default (WrappedComponent) => {
|
|
|
29
29
|
WrappedComponent,
|
|
30
30
|
)})`;
|
|
31
31
|
return WithQueryString;
|
|
32
|
-
}
|
|
32
|
+
}
|
|
@@ -3,19 +3,29 @@
|
|
|
3
3
|
* @module volto-slate/blocks/Title/TitleBlockView
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from 'react';
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
|
+
import Slugger from 'github-slugger';
|
|
9
|
+
import { renderLinkElement } from '@plone/volto-slate/editor/render';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* View title block component.
|
|
11
13
|
* @class View
|
|
12
14
|
* @extends Component
|
|
13
15
|
*/
|
|
14
|
-
const TitleBlockView = ({ properties, metadata }) => {
|
|
16
|
+
const TitleBlockView = ({ properties, metadata, id, children }) => {
|
|
17
|
+
let attr = { id };
|
|
18
|
+
const title = (properties || metadata)['title'];
|
|
19
|
+
const slug = Slugger.slug(title);
|
|
20
|
+
attr.id = slug || id;
|
|
21
|
+
const LinkedTitle = useMemo(() => renderLinkElement('h1'), []);
|
|
15
22
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
<LinkedTitle
|
|
24
|
+
mode="view"
|
|
25
|
+
children={title ?? children}
|
|
26
|
+
attributes={attr}
|
|
27
|
+
className={'documentFirstHeading'}
|
|
28
|
+
/>
|
|
19
29
|
);
|
|
20
30
|
};
|
|
21
31
|
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
6
|
import View from './View';
|
|
4
7
|
|
|
8
|
+
const mockStore = configureStore();
|
|
9
|
+
|
|
5
10
|
test('renders a view title component', () => {
|
|
11
|
+
const store = mockStore({
|
|
12
|
+
intl: {
|
|
13
|
+
locale: 'en',
|
|
14
|
+
messages: {},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
6
17
|
const component = renderer.create(
|
|
7
|
-
<
|
|
18
|
+
<Provider store={store}>
|
|
19
|
+
<MemoryRouter>
|
|
20
|
+
<View properties={{ title: 'My Title' }} id="a123" />
|
|
21
|
+
</MemoryRouter>
|
|
22
|
+
</Provider>,
|
|
8
23
|
);
|
|
9
24
|
const json = component.toJSON();
|
|
10
25
|
expect(json).toMatchSnapshot();
|
|
@@ -56,7 +56,14 @@ const View = (props) => {
|
|
|
56
56
|
const items = [];
|
|
57
57
|
if (!level || !levels.includes(level)) return;
|
|
58
58
|
tocEntriesLayout.push(id);
|
|
59
|
-
tocEntries[id] = {
|
|
59
|
+
tocEntries[id] = {
|
|
60
|
+
level,
|
|
61
|
+
title: title || block.plaintext,
|
|
62
|
+
items,
|
|
63
|
+
id,
|
|
64
|
+
override_toc: block.override_toc,
|
|
65
|
+
plaintext: block.plaintext,
|
|
66
|
+
};
|
|
60
67
|
if (level < rootLevel) {
|
|
61
68
|
rootLevel = level;
|
|
62
69
|
}
|
|
@@ -8,15 +8,27 @@ import PropTypes from 'prop-types';
|
|
|
8
8
|
import { map } from 'lodash';
|
|
9
9
|
import { List } from 'semantic-ui-react';
|
|
10
10
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
11
|
+
import { useHistory } from 'react-router-dom';
|
|
11
12
|
import AnchorLink from 'react-anchor-link-smooth-scroll';
|
|
13
|
+
import Slugger from 'github-slugger';
|
|
12
14
|
|
|
13
|
-
const RenderListItems = ({ items, data }) => {
|
|
15
|
+
const RenderListItems = ({ items, data, history }) => {
|
|
14
16
|
return map(items, (item) => {
|
|
15
|
-
const { id, level, title } = item;
|
|
17
|
+
const { id, level, title, override_toc, plaintext } = item;
|
|
18
|
+
const slug = override_toc
|
|
19
|
+
? Slugger.slug(plaintext)
|
|
20
|
+
: Slugger.slug(title) || id;
|
|
16
21
|
return (
|
|
17
22
|
item && (
|
|
18
23
|
<List.Item key={id} className={`item headline-${level}`} as="li">
|
|
19
|
-
<AnchorLink
|
|
24
|
+
<AnchorLink
|
|
25
|
+
href={`#${slug}`}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
history.push({ hash: slug });
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{title}
|
|
31
|
+
</AnchorLink>
|
|
20
32
|
{item.items?.length > 0 && (
|
|
21
33
|
<List
|
|
22
34
|
ordered={data.ordered}
|
|
@@ -38,6 +50,7 @@ const RenderListItems = ({ items, data }) => {
|
|
|
38
50
|
* @extends Component
|
|
39
51
|
*/
|
|
40
52
|
const View = ({ data, tocEntries }) => {
|
|
53
|
+
const history = useHistory();
|
|
41
54
|
return (
|
|
42
55
|
<>
|
|
43
56
|
{data.title && !data.hide_title ? (
|
|
@@ -57,7 +70,7 @@ const View = ({ data, tocEntries }) => {
|
|
|
57
70
|
bulleted={!data.ordered}
|
|
58
71
|
as={data.ordered ? 'ol' : 'ul'}
|
|
59
72
|
>
|
|
60
|
-
<RenderListItems items={tocEntries} data={data} />
|
|
73
|
+
<RenderListItems items={tocEntries} data={data} history={history} />
|
|
61
74
|
</List>
|
|
62
75
|
</>
|
|
63
76
|
);
|