Open tiberiuichim opened 2 years ago
A field in a client-side schema can have a default
property and it would represent the initial value in the rendered schema.
For block defaults to work properly, you need to pass the onChangeBlock
, here's the most important props: <BlockDataForm block={block} schema={schema} onChangeBlock={onChangeBlock} />
When you write a "blocks in a single block" type of thing, make sure to properly pass selected as false (or true) to the inner blocks to avoid focusing problems. You'll probably also want to set blockHasOwnFocusManagement
to true.
Volto's Pastanaga theme doesn't include all SemanticUI icons because it loads the fonts from the "basic" semantic-ui theme, not the "default" semantic-ui theme. You have to copy those into your theme or set @icon
to default
in theme.config
.
If you see a console warning from a component and you don't know which is that component, use the React Developer Tools browser extension, the Components tab, as it has quick links at the top to the components with warnings.
Every time you add a new customized file you have to restart Volto.
getContent(getBaseUrl(item['@id']))
won't be ok with Volto 14 ++api++ calls. It needs to be getContent(flattenToAppURL(getBaseUrl(item['@id'])))
Q: Can I use another component library with Volto?
A: Yes! React integration is pretty straight forward, the CSS/styling part is probably less straightforward, but there's no issues there. You could be thinking of using a completely separate set of components + styling for the public site (and your blocks), while Volto uses the semantic-ui-react component for the CMS part (editing interface, block edit wrappers, sidebars, etc).
The key
param to a React component can be used even outside somelist.map()
iterations, as a "cache busting" , in case the particular component needs to be recreated, if it keeps some internal state and refuses to respect outside props changing. For example:
<Dropdown defaultValue={x} options={y} />
If x
changes, the semantic-ui Dropdown won't acknowledge it, so you can force the recreation of the dropdown with:
<Dropdown key={x} defaultValue={x} options={y} />
getContent(getBaseUrl(item['@id']))
won't be ok with Volto 14 ++api++ calls. It needs to begetContent(flattenToAppURL(getBaseUrl(item['@id'])))
No sure about this one, are you sure? the id's should be "clean" if ++api++ is used, if not, the deployment is wrong.
If you're doing work on the Volto SSR server, you can restart that server by typing rs<enter>
(in the terminal where yarn start
is running).
properties
vs metadata
. Block edit components get passed these two fields, they seem to be identical, but in the case of the "block inside block", the "metadata = content metadata", while "properties = parent block data".
injectLazyLibs
vs loadable(() => import('SomeComponent'))
. Both should be used! Straight loadable
should be used for components, injectLazyLibs
should be used for utility code (libraries) or components that are not exported as default exports. injectlazyLibs
implements the following pattern: "is the library loaded? if so, render the wrapped component, otherwise don't render anything".
You can try a custom version of Volto in a Volto project using yalc. Inside Volto, run yalc publish
, inside your Volto project run: yalc add @plone/volto --no-pure
. Note that this technique can be used not only for Volto, but for third-party packages as well (if you want to try a package that hasn't been published yet, or to do custom development on it).
When manually writing a schema to generate a block settings form, be aware that you can add any properties in the form field definition, according to what the FormFieldWrapper
or the wrapped widget component receive. For example, you could pass wrapped: false
to render a widget in its simplest form, with no widget decorations (no grid, no label, no description, etc).
Why does Volto use Bearer token-based authentication instead of an authentication cookie? Cookies can't cross domains and, given the proper cors permissions, the json endpoints api path can be set to any website.
Example to overwrite existing cypress commands to add a delay between commands:
// cypress/support/commands.js
const COMMAND_DELAY = 1000;
for (const command of [
'visit',
'click',
'type',
'clear',
'contains',
]) {
Cypress.Commands.overwrite(command, (originalFn, ...args) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(originalFn(...args));
}, COMMAND_DELAY);
});
});
}
More information about custom commands and overwriting existing commands: https://on.cypress.io/custom-commands
How to test a Volto feature branch with a Volto project:
In you local clone of plone/volto clone, checkout the desired feature branch. Then run
yalc publish
Yalc is a global utility, similar to mrs-developer. You have to install it with:
npm install -g yalc
Then, in your Volto project, you can now run:
yalc add @plone/volto --no-pure
This "installs" the "locally published copy of @plone/volto" in your project. It makes a hard copy of it and it changes package.json, so beware not to push that in your sourcecode repo. Now you can test Volto with your project. Once you're done, you can remove the local copy of volto with
yalc retreat
Notice that the workflow is pretty cumbersome if you want to do development of Volto, as you have to always do the dance of publish+add on every change in your original Volto clone.
You can debug backend calls done by the SSR nodejs server with DEBUG=superagent yarn start
.
How to test a Volto feature branch with a Volto project, part 2 (unconfirmed):
In mrs-developer.json add your volto branch:
{
"volto": {
"url": "git@github.com:plone/volto.git",
"https": "https://github.com/plone/volto.git",
"package": "@plone/volto",
"branch": "slots_quanta_split_relative_path",
"develop": true,
"path": ""
}
}
In the generated jsconfig.json, make sure that the 'src' is not a part of the volto path:
{
"compilerOptions": {
"paths": {
"@plone/volto": [
"addons/volto"
],
...
When you shadow a Volto module, always check if there's any relative imports such as: https://github.com/plone/volto/blob/caeaaacb724e4846f519cccafca4b984f38ee4d2/src/components/theme/View/SummaryView.jsx#L11
In this case, you need to rewrite the imports, for example:
import { PreviewImage } from '@plone/volto/components';
If you need to login and switch between cypress dedicated / regular volto, the Volto Login chrome extension is handy: https://github.com/collective/plone6-autologin-extension
Volto supports multiple customization paths in each addon, you can specify them in the package.json:customizationPaths
. It's an array of strings (paths) relative to the root of the package.
How to debug jest tests:
debugger
lines in your testnode --inspect-brk node_modules/.bin/jest --runInBand __tests__/volto-slate
Did you know you can use JSX in schema texts? For example:
properties: {
use_live_data: {
type: 'boolean',
title: 'Use live data',
default: true,
},
hover_format_xy: {
type: 'string',
title: 'Hover format',
placeholder: '',
description: (
<>
See{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format"
>
D3 format documentation
</a>
</>
),
}
For route-based views, you may want to tweak the breadcrumbs. Here's a way to do this from the useEffect of the route view component (example from a live project, adjust to your needs):
React.useEffect(() => {
const handler = async () => {
if (item) {
const action = {
type: 'GET_BREADCRUMBS_SUCCESS',
result: {
items: [
{
title: 'Datahub',
'@id': '/en/datahub',
},
{
title: rawTitle,
'@id': `/en/datahub/view/${docid}`,
},
],
},
};
await dispatch({ type: 'GET_BREADCRUMBS_PENDING' }); // satisfy content load protection
await dispatch(action);
}
};
handler();
}, [item, dispatch, docid, rawTitle]);
Edit: this no longer works in Volto 17.
If the Plone site is not named "Plone" (for example, I have a Plone site that's at http://localhost:8080/cca
), I have to start volto with:
env RAZZLE_API_PATH=http://localhost:8080/cca yarn start
# or for production check:
yarn build
env RAZZLE_API_PATH=http://localhost:8080/cca yarn start:prod
To avoid always having to add the env var, I can add a .env.development
file inside the Volto project root with the env vars inside it:
RAZZLE_API_PATH=http://localhost:8080/cca
You can debug devproxy issues withDEBUG_HPM=1
If you get 404 from the endpoint JSON calls and everything looks fine, you may have an old version of plone.rest, which implements the ++api++
traverser.
we can use <UniversalLink/>
to render all types of links in a typical volto project.
If the Plone site is not named "Plone" (for example, I have a Plone site that's at
http://localhost:8080/cca
), I have to start volto with:env RAZZLE_API_PATH=http://localhost:8080/cca
To avoid always having to add the env var, I can add a
.env.development
file inside the Volto project root with the env vars inside it:RAZZLE_API_PATH=http://localhost:8080/cca
Actually I think this is not really good, as it's not using "seamless mode". RAZZLE_DEV_PROXY_API_PATH
should be used.
You can enable HotReloading for a node_modules library by adding something like this in your razzle.config.js
(or razzle.extend.js
)
if (config.devServer) {
config.devServer.watchOptions.ignored = /node_modules\/(?!(@plone\/volto|@elastic))/g;
}
If you have to tweak a schema for a block, it must be done in a schema enhancer. Don't do it in the block edit component, as that makes it difficult to do (future) data validations and block default values.
This may not be obvious, but if you have code like:
const { facetOptions } = React.useState(getFacetOptions());
getFacetOptions()
will be called on every rendering of our component. It's obvious, of course, if you would consider the code as the equivalent:
const { facetOptions } = irelevantFunction(getFacetOptions());
If you're building docker images, make sure not to have the *.yml
in .dockerignore
, as the .yarnrc.yml
, with its setup needs to be there!
defaultSemverRangePrefix: ""
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.2.3.cjs
If you don't do this, then yarn will use a different strategy, which is incompatible with razzle (it won't find webpack)
Solves errors such as:
Error: razzle tried to access webpack (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound.
Here's my procedure for upgrading a Volto project to latest volto (major upgrades):
yo @plone/volto <project-name>
to generate a new Volto project skeletonmrs.developer.json
, jsconfig.json
from old projectmake develop
dependencies
, resolutions
, addons
and workspaces
from the old project's package.json
to the new package.jsonyarn
src
and theme
folders coming from the old projectyarn start
omelette
, node_modules
and locales
folders) and I overwrite the files and folders in the old project. This way I keep complete git history and I only need a single commitIf you need to ship an addon with a static file that you have to reference from code, you can use the static file loader. For example:
import dummyPath from './myfile.dummy';
function DummyLink() {
return <a href={dummyPath}>Dummy</a>
}
The webpack file loader is configured as a default fallback loader, so if you want to reference JS or JSON files, make sure to rename them with a different extension. The result of importing the static file is a path generated by webpack.
How to change the default language for a website to something other then English, if you have created your Plone site as default English:
If you don't do this, all redirections for new visitors go to the language of the root content item, which is English.
A question often asked: can I work on X ticket in Volto? The answer is always, yes, but here's more context for it:
One of the core principles in Plone (Volto's backend, and by extension also Volto) is that our software is a do-ocracy. Things will get done only if we start working on them. So, really, there's no task in Volto that's off-limits. There are, though, limits to the things that can be approached, realistically, by new contributors. There's back history, context, understanding of the overall vision, etc, that all come with experience.
Also, most important to read: https://6.dev-docs.plone.org/volto/developer-guidelines/contributing.html
How to add an emergency user with the new plone-backend docker image. With a running plone-backend container, where the service is called plone
, run:
docker compose exec plone ./docker-entrypoint.sh bash
bin/addzopeuser -c etc/zeo.conf myuser mypass
Another method:
docker compose exec plone ./docker-entrypoint.sh console
Inside the debug console, run:
>>> app.acl_users._doAddUser('myuser', 'mypassword', ['Manager'], [])
<PropertiedUser 'myuser'>
>>> import transaction; transaction.commit()
Exit the console with ctrl+d
. Make sure you have a return value for _doAddUser
, otherwise the user is not created because it exists.
How to use plone-backend for development purposes. The biggest problem is that it's slow to start when you have DEVELOP
addons.
So, to develop an addon with that image, you can rewrite the plone service command to something like (docker-compose.yml
):
command: bash
tty: true
Now start a shell to the container with:
docker compose exec plone bash
./docker-entrypoint.sh bash
bin/runwsgi -v etc/zope.ini config_file=zeo.conf
By running the docker-entrypoint.sh bash
command, it will start a subshell that has all the required environment variables setup. Now starting the zope wsgi server is very fast, as it doesn't have to do all the pip install
commands.
For relstorage,
docker compose exec backend bash
./docker-entrypoint.sh bash
bin/zconsole debug etc/relstorage.conf
If you need to provide a facet for a custom "field" for the search block, follow this semi-guide: https://github.com/plone/volto/issues/3020#issuecomment-1385542090
Using nvm helps your Volto development. If your environment doesn't let you install global packages with npm install -g mrs-developer
unless you use sudo
, it's a sign you're doing something wrong. You should really use nvm or another node version manager.
Getting settings directly from the config in @plone/volto/registry
is convenient, but it makes it hard to override them for a specific context. It's better to make it a prop for your component, and then fall back to getting a default from the config.
The default html block in Volto is "restricted", by having its HTML content processed by Plone's safe html transform.
You can create an "escape" html block, that's unrestricted, by simply doing something like:
config.blocks.blocksConfig.plainHtml = {
...config.blocks.blocksConfig.html,
id: 'plainHtml',
};
config.blocks.blocksConfig.html.restricted = true;
This is because the block transformer in plone.restapi is restricted to a block with id html
.
API class does not support array parameters see #4349 for details. One work around that worked for me is to serialize/deserialize your object before/after the API. For example, if you passed the following object to API.get
:
{
searchTerm: 'power',
page: 1,
perPage: 10,
advancedFields: [
{name: 'field1', value:'val for field 1'},
{name: 'field2', value:'val for field 2'}
{name: 'field3', value:'val for field 3'}
]
}
By default there is no support by the API class to handle array parameters advancedFields
. But since Plone support []
notation. For my case the following two functions solved my issue:
const serialize = (obj, prefix) => {
var str = [],
p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
var k = prefix ? prefix + "[" + p + "]" : p,
v = obj[p];
str.push((v !== null && typeof v === "object") ?
serialize(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return str.join("&");
}
// Incase needed.
const deserialize = (str) => {
var obj = {};
var pairs = str.replace("?", "").split("&");
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
var indexOfEqual = pair.indexOf("=");
var key = decodeURIComponent(pair.substring(0, indexOfEqual));
var value = decodeURIComponent(pair.substring(indexOfEqual + 1));
if (!isNaN(value) && value !== "") {
value = Number(value);
} else if (value === 'true') {
value = true;
} else if (value === 'false') {
value = false;
}
var keys = key.split("[").map((k) => k.replace("]", ""));
var lastKey = keys.pop();
var currentObj = obj;
for (var j = 0; j < keys.length; j++) {
var innerKey = keys[j];
if (!currentObj[innerKey]) {
currentObj[innerKey] = {};
}
currentObj = currentObj[innerKey];
}
currentObj[lastKey] = value;
}
return obj;
}
Both tested for 2 levels.
If one of your dependencies is shipped as an mjs module, you'll have to get Babel to compile, before it can be included in the Volto bundle. You can do this from any addon with a razzle.extend.js
(of course, this can be done from a razzle.config.js
as well, but code has to be adjusted):
const path = require('path');
const makeLoaderFinder = require('razzle-dev-utils/makeLoaderFinder');
const modify = (config, { target, dev }, webpack) => {
const medusaPath = path.dirname(
require.resolve('@medusajs/medusa-js'),
);
const babelLoaderFinder = makeLoaderFinder('babel-loader');
const babelLoader = config.module.rules.find(babelLoaderFinder);
const { include } = babelLoader;
include.push(medusaPath);
return config;
};
module.exports = {
plugins: (plugs) => plugs,
modify,
};
routers:
frontend:
rule: "Host(`localhost`)"
service: frontend
backend:
rule: "Host(`localhost`) && PathPrefix(`/++api++`)"
service: backend
middlewares:
- backend
middlewares:
backend:
replacePathRegex:
regex: "^/\\+\\+api\\+\\+($|/.*)"
replacement: "/VirtualHostBase/http/localhost/plone/++api++/VirtualHostRoot$1"
services:
frontend:
loadBalancer:
servers:
- url: "http://host.docker.internal:3000"
backend:
loadBalancer:
servers:
- url: "http://host.docker.internal:55001"
Also https://github.com/plone/volto/blob/33962f130f25a92760848c07ab59bcd13a2ef37d/docker-compose.yml#L14
When using Apache as frontend proxy, I had to set to allow seamless mode to work properly. Maybe relevant, my frontend (Volto) was running in its own Docker container. See docs https://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost
ProxyPreserveHost On
To use traefik as a backend for development, you need to add in your local .env.development
:
RAZZLE_DEV_PROXY_API_PATH=http://plone.docker.localhost:8090/Plone
RAZZLE_DEV_PROXY_INSECURE=true
When you shadow a Volto file, you can minimize the impact by doing something like this (where we shadow the helpers/Blocks/Blocks.js
module:
import * as original from '@plone/volto-original/helpers/Blocks/Blocks';
original.X = X; // you can rewrite functions from the original module, or add new ones
module.exports = original;
I think we need a big list of small volto rules. Things that only need one line, just the rule. These are very much context dependent rules, maybe not known, obscure, whatever. Let's try to document them here, maybe? And then we can better organize our docs.
Edit: this turned into a FAQ sort of thing. Oh well...