feat: Adjust copy for setup.

This commit is contained in:
Ruben Verborgh 2021-09-23 19:39:55 +01:00 committed by Joachim Van Herwegen
parent b592d449eb
commit 34a44d1636
7 changed files with 152 additions and 175 deletions

View File

@ -1,5 +1,7 @@
<% const isBlankForm = !('prefilled' in locals); %> <%
<% prefilled = locals.prefilled || {}; %> const isBlankForm = !('prefilled' in locals);
prefilled = locals.prefilled || {};
%>
<fieldset> <fieldset>
<legend>Your WebID</legend> <legend>Your WebID</legend>
@ -47,37 +49,35 @@
<fieldset> <fieldset>
<legend>Your Pod</legend> <legend>Your Pod</legend>
<p> <p>
A Pod is a place to store your data. A Pod is a storage place for your data.
<br>
If you create a new WebID, you must also create a Pod to store that WebID.
</p> </p>
<ol> <ol>
<li class="checkbox"> <li class="checkbox">
<label> <label>
<input type="checkbox" id="createPod" name="createPod"<% <input type="checkbox" id="createPod" name="createPod"<%
if (isBlankForm || prefilled.createPod) { %> checked<% } %>> if (isBlankForm || prefilled.createPod) { %> checked<% } %>>
Create a new Pod with my WebID as owner Create a new Pod with my WebID as owner<% if (!locals.allowRoot) { %>.<% } %>
</label> </label>
<ol id="createPodForm"> <ol id="createPodForm">
<li class="radio" id="rootPodOnForm"> <% if (locals.allowRoot) { %>
<label> <li class="radio">
<input type="radio" id="rootPodOn" name="rootPod" value="on"<% <label>
if (locals.allowRoot && (isBlankForm || prefilled.rootPod)) { %> checked<% } %>> <input type="radio" id="rootPodOn" name="rootPod" value="on"<%
... in the root. if (isBlankForm || prefilled.rootPod) { %> checked<% } %>>
</label> …in the root.
</li> </label>
<li class="radio"> </li>
<label> <li class="radio">
<input type="radio" id="rootPodOff" name="rootPod" value=""<% <label>
if (!locals.allowRoot || (!isBlankForm && !prefilled.rootPod)) { %> checked<% } %>> <input type="radio" id="rootPodOff" name="rootPod" value=""<%
... in its own namespace. if (!isBlankForm && !prefilled.rootPod) { %> checked<% } %>>
</label> …in its own namespace.
<ol id="podNameForm"> </label>
<li> </li>
<label for="podName">Pod name:</label> <% } %>
<input id="podName" type="text" name="podName" value="<%= prefilled.podName || '' %>"> <li id="podNameForm">
</li> <label for="podName">Pod name:</label>
</ol> <input id="podName" type="text" name="podName" value="<%= prefilled.podName || '' %>">
</li> </li>
</ol> </ol>
</li> </li>
@ -116,54 +116,60 @@
</div> </div>
</fieldset> </fieldset>
<!-- Assist the user with filling out the form by hiding irrelevant fields -->
<script> <script>
// Assist the user with filling out the form by hiding irrelevant fields
(() => {
// Wire up the UI elements
const elements = {}; const elements = {};
// Wires up the DOM element with the specified ID
function registerElement(id) {
const element = document.getElementById(id) || document.createElement('input');
elements[id] = element;
element.addEventListener('change', synchronizeInputFields);
}
// Wire up all elements
[ [
'mainForm',
'createWebIdOn', 'createWebIdOff', 'createWebIdForm', 'existingWebIdForm', 'webId', 'createWebIdOn', 'createWebIdOff', 'createWebIdForm', 'existingWebIdForm', 'webId',
'createPod', 'createPodForm', 'rootPodOnForm', 'rootPodOn', 'rootPodOff', 'podNameForm', 'podName', 'createPod', 'createPodForm', 'rootPodOn', 'rootPodOff', 'podNameForm', 'podName',
'register', 'passwordForm', 'noPasswordForm', 'register', 'passwordForm', 'noPasswordForm',
].forEach(id => { ].forEach(registerElement);
elements[id] = document.getElementById(id);
elements[id].addEventListener('change', updateUI);
});
elements.mainForm = document.getElementById('<%= formId %>');
updateUI(); // Conditions under which elements should be visible
const visibilityConditions = {
createWebIdForm: () => elements.createWebIdOn.checked,
existingWebIdForm: () => elements.createWebIdOff.checked,
createPodForm: () => elements.createPod.checked,
podNameForm: () => !elements.rootPodOn.checked,
passwordForm: () => elements.createWebIdOn.checked || elements.register.checked,
noPasswordForm: () => !isVisible('passwordForm'),
};
// Updates the UI when something has changed // Ensures that the only relevant input fields are visible and enabled
function updateUI({ srcElement } = {}) { function synchronizeInputFields({ srcElement } = {}) {
// When Pod creation is required, automatically tick the corresponding checkbox // The user needs a Pod if they want to create a WebID
if (elements.createWebIdOn.checked) if (elements.createWebIdOn.checked)
elements.createPod.checked = true; elements.createPod.checked = true;
elements.createPod.disabled = elements.createWebIdOn.checked;
// Hide irrelevant fields // Hide irrelevant fields
setVisibility('createWebIdForm', elements.createWebIdOn.checked); for (const [id, condition] of Object.entries(visibilityConditions))
setVisibility('existingWebIdForm', elements.createWebIdOff.checked); setVisibility(id, condition());
setVisibility('createPodForm', elements.createPod.checked);
setVisibility('rootPodOnForm', <%= locals.allowRoot %>); // Lock pod creation if a WebID is requested
setVisibility('podNameForm', elements.rootPodOff.checked); elements.createPod.disabled = elements.createWebIdOn.checked;
setVisibility('passwordForm', elements.createWebIdOn.checked || elements.register.checked);
setVisibility('noPasswordForm', !isVisible('passwordForm'));
// If child elements have just been activated, focus on them // If child elements have just been activated, focus on them
if (srcElement?.checked) { if (srcElement?.checked) {
switch(document.activeElement) { switch(document.activeElement) {
case elements.createWebIdOff: case elements.createWebIdOff:
const { webId } = elements; const { webId } = elements;
webId.value = webId.value.startsWith('http') ? webId.value : 'https://'; webId.value = webId.value.startsWith('http') ? webId.value : 'https://';
webId.focus(); webId.focus();
break; break;
case elements.createPod: case elements.createPod:
if (elements.rootPodOn.checked) { case elements.rootPodOff:
break; elements.podName.focus();
} break;
case elements.rootPodOff:
elements.podName.focus();
break;
} }
} }
} }
@ -182,8 +188,8 @@
// Disable children of hidden elements, // Disable children of hidden elements,
// such that the browser does not expect input for them // such that the browser does not expect input for them
for (const child of getDescendants(element)) { for (const child of getDescendants(element)) {
if ('disabled' in child) if ('disabled' in child)
child.disabled = !visible; child.disabled = !visible;
} }
} }
@ -192,12 +198,18 @@
return [...element.querySelectorAll("*")]; return [...element.querySelectorAll("*")];
} }
// TODO: take form id as input? // Prepare the form when the DOM is ready
window.addEventListener('DOMContentLoaded', (event) => {
synchronizeInputFields();
elements.mainForm.classList.add('loaded');
});
// Enable all elements on form submission (otherwise their value is not submitted) // Enable all elements on form submission (otherwise their value is not submitted)
elements.mainForm.addEventListener('submit', () => { elements.mainForm.addEventListener('submit', () => {
for (const child of getDescendants(elements.mainForm)) for (const child of getDescendants(elements.mainForm)) {
child.disabled = false; if (child.name)
child.disabled = false;
}
}); });
elements.mainForm.addEventListener('formdata', updateUI); elements.mainForm.addEventListener('formdata', synchronizeInputFields);
})();
</script> </script>

View File

@ -5,7 +5,7 @@
<p class="error">Error: <%= message %></p> <p class="error">Error: <%= message %></p>
<% } %> <% } %>
<%- include('./register-partial.html.ejs', { allowRoot: false, formId: 'mainForm' }) %> <%- include('./register-partial.html.ejs', { allowRoot: false }) %>
<p class="actions"><button type="submit" name="submit">Sign up</button></p> <p class="actions"><button type="submit" name="submit">Sign up</button></p>
</form> </form>

View File

@ -22,22 +22,21 @@
<h2 id="users">Getting started as a <em>user</em></h2> <h2 id="users">Getting started as a <em>user</em></h2>
<p> <p>
<strong><a href="/idp/register/">Sign up</a> for an account</strong> <a href="/idp/register/">Sign up for an account</a>
to get started with your own Pod and WebID. to get started with your own Pod and WebID.
</p> </p>
<p> <p>
The <em>default</em> configuration stores data only in memory, The default configuration stores data only in memory.
so be sure to choose a configuration that saves data to disk. If you want to keep data permanently,
If you are exposing this server publicly, choose a configuration that saves data to disk instead.
<a href="#public">read the guidelines below</a>.
</p> </p>
<h2 id="developers">Getting started as a <em>developer</em></h2> <h2 id="developers">Getting started as a <em>developer</em></h2>
<p> <p>
The <em>default</em> server configuration includes <a href="./setup">Run the setup</a> to configure your server.
this <strong>ready-to-use root Pod</strong> you're looking at. <br>
That way, you don't need to create an account The default configuration includes
to read and write data or to test apps. the <strong>ready-to-use root Pod</strong> you're currently looking at.
</p> </p>
<p> <p>
You can easily choose any folder on your disk You can easily choose any folder on your disk
@ -46,28 +45,6 @@
Use the <code>--help</code> switch to learn more. Use the <code>--help</code> switch to learn more.
</p> </p>
<h2 id="public">Making this server public</h2>
<p>
Before making this server public,
you might want to <strong>disable certain convenience features</strong>
so they remain only accessible to you:
</p>
<ul>
<li>
Modify or delete this welcome document
at <a href="index.html"><code>index.html</code></a>.
</li>
<li>
Prevent public write and control access to the root Pod
by modifying <a href="../../../.acl"><code>.acl</code></a>.
</li>
<li>
Disable Pod registration
by <a href="https://github.com/solid/community-server/blob/main/config/identity/README.md">changing
the configuration</a>.
</li>
</ul>
<h2>Have a wonderful Solid experience</h2> <h2>Have a wonderful Solid experience</h2>
<p> <p>
<strong>Learn more about Solid <strong>Learn more about Solid

View File

@ -1,39 +1,7 @@
<h1>Welcome to Solid</h1> <h1>Set up your Solid server</h1>
<p> <p>
This server implements Your Solid server needs a <strong>one-time setup</strong>
the <a href="https://solid.github.io/specification/protocol">Solid protocol</a> so it acts exactly the way you want.
so you can create your own <a href="https://solidproject.org/about">Solid Pod</a>
and identity.
</p>
<h2 id="public">Making this server public</h2>
<p>
Before making this server public,
you might want to <strong>disable Pod registration</strong>
by <a href="https://github.com/solid/community-server/blob/main/config/identity/README.md">changing
the configuration</a>.
</p>
<h2 id="setup">Setting up the server</h2>
<p>
The <em>default</em> configuration stores data only in memory,
so be sure to choose a configuration that saves data to disk.
If you are exposing this server publicly,
<a href="#public">read the guidelines below</a>.
</p>
<p>
When using the file-based version of the server,
you can easily choose any folder on your disk to use as root.
<br>
Use the <code>--help</code> switch to learn more.
</p>
<p>
To make sure the server is set up exactly as you want it,
please fill in the form below.
</p>
<p>
In case you want to automate the server initialization and want to get rid of this setup screen,
update your config with new imports from <code>config/app/setup/</code> and possibly <code>config/app/init/</code>.
</p> </p>
<form method="post" id="mainForm"> <form method="post" id="mainForm">
@ -43,46 +11,62 @@
<p class="error"><%= message %></p> <p class="error"><%= message %></p>
<% } %> <% } %>
<fieldset> <fieldset>
<legend>Choose options</legend> <legend>Accounts on this server</legend>
<ol> <ol>
<li class="checkbox"> <li class="checkbox">
<label> <label>
<input type="checkbox" id="initialize" name="initialize" checked> <input type="checkbox" checked disabled>
Allow access to the root container. Enable account registration.
</label> </label>
<p> <p>
This defaults to public access for everyone. You can disable account registration
Disabling this makes it impossible to access the root container and add resources, by <a href="https://github.com/solid/community-server/blob/main/config/identity/README.md">changing the configuration</a>.
but new pods can still be created through registration,
which is ideal if you only want data to be edited in the pods.
This option is irrelevant when creating a root pod with the option below.
</p> </p>
</li> </li>
<li class="checkbox"> <li class="checkbox">
<label> <label>
<input type="checkbox" id="registration" name="registration" checked> <input type="checkbox" id="registration" name="registration">
Provision a pod, create a WebID, and/or register an identity. Sign me up for an account.
</label> </label>
<p>
Any existing root Pod will be disabled.
</p>
</li>
<li class="checkbox" id="initializeForm">
<label>
<input type="checkbox" id="initialize" name="initialize">
Expose a public root Pod.
</label>
<p>
By default, the public has read and write access to the root Pod.
<br>
You typically only want to choose this
for rapid testing and development.
</p>
</li> </li>
</ol> </ol>
</fieldset> </fieldset>
<fieldset id="registrationForm"> <fieldset id="registrationForm">
<%- include('../identity/email-password/register-partial.html.ejs', { allowRoot: true, formId: 'mainForm' }) %> <legend>Sign up</legend>
<%-
include('../identity/email-password/register-partial.html.ejs', {
allowRoot: true,
})
%>
</fieldset> </fieldset>
<p class="actions"><button type="submit" name="submit">Submit</button></p> <p class="actions"><button type="submit">Complete setup</button></p>
</form> </form>
<!-- Show or hide the account creation form when needed -->
<script> <script>
const registrationCheckbox = document.getElementById('registration'); [
registrationCheckbox.addEventListener('change', updateUI); 'registration', 'registrationForm', 'initializeForm',
const registrationForm = document.getElementById('registrationForm'); ].forEach(registerElement);
function updateUI() { Object.assign(visibilityConditions, {
const visible = registrationCheckbox.checked; registrationForm: () => elements.registration.checked,
registrationForm.classList[visible ? 'remove' : 'add']('hidden'); initializeForm: () => !elements.registration.checked,
} });
updateUI();
</script> </script>

View File

@ -1,24 +1,21 @@
<h1 id="public">Server successfully set up</h1> <h1 id="public">Server setup complete</h1>
<p>
Congratulations!
Your Solid server is now ready to use.
<br>
You can now visit its <a href="./">homepage</a>.
</p>
<% if (initialize && !registration) { %> <% if (initialize && !registration) { %>
<h2>Root Pod</h2>
<p> <p>
You have chosen to allow the root container to be accessible. <strong>The root Pod is publicly accessible.</strong>
<br>
Prevent public write and control access to the root Prevent public write and control access to the root
by modifying <a href=".acl"><code>.acl</code></a>. by modifying its <a href=".acl">ACL document</a>.
</p> </p>
<% } %> <% } %>
<% if (registration) { %> <% if (registration) { %>
<%- include('../identity/email-password/register-response-partial.html.ejs', { authenticating: false }) %> <%- include('../identity/email-password/register-response-partial.html.ejs', { authenticating: false }) %>
<% } %> <% } %>
<h2>Have a wonderful Solid experience</h2>
<p>
<strong>Learn more about Solid
at <a href="https://solidproject.org/">solidproject.org</a>.</strong>
</p>
<p>
You are warmly invited
to <a href="https://github.com/solid/community-server/discussions">share your experiences</a>
and to <a href="https://github.com/solid/community-server/issues">report any bugs</a> you encounter.
</p>

View File

@ -151,6 +151,12 @@ fieldset > legend {
fieldset > legend + p { fieldset > legend + p {
margin: 0 0 .5em .25em; margin: 0 0 .5em .25em;
} }
fieldset > fieldset > legend {
font-size: 1.2em;
}
.hidden legend {
display: none;
}
fieldset ol { fieldset ol {
list-style: none; list-style: none;
margin: 0; margin: 0;
@ -170,6 +176,7 @@ fieldset ol ol li:not(.checkbox, .radio) {
} }
fieldset li label { fieldset li label {
font-weight: 600; font-weight: 600;
cursor: pointer;
} }
fieldset li li label { fieldset li li label {
font-weight: 500; font-weight: 500;
@ -220,7 +227,7 @@ form ul.actions > li {
} }
form.loaded * { form.loaded * {
max-height: 500px; max-height: 1000px;
transition: max-height .2s; transition: max-height .2s;
} }
form .hidden { form .hidden {

View File

@ -33,21 +33,21 @@ describe('A Solid server with setup', (): void => {
it('catches all requests.', async(): Promise<void> => { it('catches all requests.', async(): Promise<void> => {
let res = await fetch(baseUrl, { method: 'GET', headers: { accept: 'text/html' }}); let res = await fetch(baseUrl, { method: 'GET', headers: { accept: 'text/html' }});
expect(res.status).toBe(200); expect(res.status).toBe(200);
await expect(res.text()).resolves.toContain('Welcome to Solid'); await expect(res.text()).resolves.toContain('Set up your Solid server');
res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'GET', headers: { accept: 'text/html' }}); res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'GET', headers: { accept: 'text/html' }});
expect(res.status).toBe(200); expect(res.status).toBe(200);
await expect(res.text()).resolves.toContain('Welcome to Solid'); await expect(res.text()).resolves.toContain('Set up your Solid server');
res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'PUT', headers: { accept: 'text/html' }}); res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'PUT', headers: { accept: 'text/html' }});
expect(res.status).toBe(405); expect(res.status).toBe(405);
await expect(res.text()).resolves.toContain('Welcome to Solid'); await expect(res.text()).resolves.toContain('Set up your Solid server');
}); });
it('can create a server that disables root but allows registration.', async(): Promise<void> => { it('can create a server that disables root but allows registration.', async(): Promise<void> => {
let res = await fetch(setupUrl, { method: 'POST', headers: { accept: 'text/html' }}); let res = await fetch(setupUrl, { method: 'POST', headers: { accept: 'text/html' }});
expect(res.status).toBe(200); expect(res.status).toBe(200);
await expect(res.text()).resolves.toContain('Server successfully set up'); await expect(res.text()).resolves.toContain('Server setup complete');
// Root access disabled // Root access disabled
res = await fetch(baseUrl); res = await fetch(baseUrl);
@ -74,7 +74,7 @@ describe('A Solid server with setup', (): void => {
body: JSON.stringify({ initialize: true }), body: JSON.stringify({ initialize: true }),
}); });
expect(res.status).toBe(200); expect(res.status).toBe(200);
await expect(res.text()).resolves.toContain('Server successfully set up'); await expect(res.text()).resolves.toContain('Server setup complete');
// Root access enabled // Root access enabled
res = await fetch(baseUrl); res = await fetch(baseUrl);
@ -99,7 +99,7 @@ describe('A Solid server with setup', (): void => {
body: JSON.stringify({ registration: true, initialize: true, ...registerParams }), body: JSON.stringify({ registration: true, initialize: true, ...registerParams }),
}); });
expect(res.status).toBe(200); expect(res.status).toBe(200);
await expect(res.text()).resolves.toContain('Server successfully set up'); await expect(res.text()).resolves.toContain('Server setup complete');
// Root profile created // Root profile created
res = await fetch(joinUrl(baseUrl, '/profile/card')); res = await fetch(joinUrl(baseUrl, '/profile/card'));