solid-panes
Version:
Solid-compatible Panes: applets and views for the mashlib and databrowser
267 lines (265 loc) • 10 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.appendProfileLinks = appendProfileLinks;
exports.createEditProfileDetailsButton = createEditProfileDetailsButton;
exports.profileLinkFields = void 0;
var _rdflib = require("rdflib");
var _solidUi = require("solid-ui");
var _icons = require("./icons");
/* The following code was generated by AI Model: GPT-5.4
Prompt: can you create me a form to enter this data [
ns.foaf('homepage'),
ns.foaf('weblog'),
ns.foaf('workplaceHomepage'),
ns.foaf('schoolHomepage')
] and put an edit button at the top left corner of the header
that when you click it pops up so you can enter this information
and save it to the Webid. You can look in
Dev2025/profile-pane/src/sections/heading/mutations to see how
to write this data in the profile. */
const profileLinkFields = exports.profileLinkFields = [{
key: 'homepage',
label: 'Homepage',
placeholder: 'https://example.com',
predicate: _solidUi.ns.foaf('homepage')
}, {
key: 'weblog',
label: 'Weblog',
placeholder: 'https://blog.example.com',
predicate: _solidUi.ns.foaf('weblog')
}, {
key: 'workplaceHomepage',
label: 'Workplace Homepage',
placeholder: 'https://company.example.com',
predicate: _solidUi.ns.foaf('workplaceHomepage')
}, {
key: 'schoolHomepage',
label: 'School Homepage',
placeholder: 'https://school.example.edu',
predicate: _solidUi.ns.foaf('schoolHomepage')
}];
function readProfileLinkValues(store, subject) {
const doc = subject.doc();
return profileLinkFields.reduce((result, field) => {
const values = store.statementsMatching(subject, field.predicate, undefined, doc).map(statement => statement.object?.value || '').filter(Boolean);
result[field.key] = values.join('\n');
return result;
}, {
homepage: '',
weblog: '',
workplaceHomepage: '',
schoolHomepage: ''
});
}
function normalizeUrlList(value, fieldLabel) {
const unique = new Set();
const lines = value.split('\n').map(entry => entry.trim()).filter(Boolean);
for (const line of lines) {
let parsed;
try {
parsed = new URL(line);
} catch {
throw new Error(`${fieldLabel} must contain valid absolute URLs.`);
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
throw new Error(`${fieldLabel} only supports http and https URLs.`);
}
unique.add(parsed.toString());
}
return [...unique];
}
async function saveProfileLinkValues(store, subject, values) {
const updater = store.updater;
if (!updater || typeof updater.update !== 'function') {
throw new Error('This profile cannot be edited with the current store.');
}
const doc = subject.doc();
const deletions = [];
const insertions = [];
for (const field of profileLinkFields) {
const existingStatements = store.statementsMatching(subject, field.predicate, undefined, doc);
deletions.push(...existingStatements);
const nextValues = normalizeUrlList(values[field.key], field.label);
for (const nextValue of nextValues) {
insertions.push((0, _rdflib.st)(subject, field.predicate, (0, _rdflib.sym)(nextValue), doc));
}
}
await new Promise((resolve, reject) => {
updater.update(deletions, insertions, (_uri, ok, errorBody) => {
if (!ok) {
reject(new Error(errorBody || 'Unable to save profile links.'));
return;
}
if (typeof store.remove === 'function' && deletions.length > 0) {
;
store.remove(deletions);
}
insertions.forEach(statement => {
store.add(statement.subject, statement.predicate, statement.object, statement.why);
});
resolve();
});
});
}
function appendProfileLinks(container, dom, store, subject) {
const webLinksSection = dom.createElement('section');
webLinksSection.className = 'social-profile-links';
container.appendChild(webLinksSection);
for (const field of profileLinkFields) {
const statements = store.statementsMatching(subject, field.predicate);
if (statements.length === 0) continue;
const uris = statements.map(statement => statement.object?.value || '').filter(Boolean).sort();
let previousUri = '';
for (const uri of uris) {
if (uri === previousUri) continue;
previousUri = uri;
let label = field.label;
if (uris.length > 1) {
const schemeIndex = uri.indexOf('//');
if (schemeIndex > 0) {
let end = uri.indexOf('/', schemeIndex + 2);
const dottedEnd = uri.lastIndexOf('.', end);
if (dottedEnd > 0) end = dottedEnd;
const hostLabel = uri.slice(schemeIndex + 2, end);
if (hostLabel) {
label = `${hostLabel} ${label}`;
}
}
}
const textNode = dom.createTextNode(label);
const anchor = dom.createElement('a');
anchor.appendChild(textNode);
anchor.setAttribute('href', uri);
const item = dom.createElement('div');
item.className = 'social-pane__link-button';
item.appendChild(anchor);
webLinksSection.appendChild(item);
}
}
}
function createEditProfileDetailsButton(options) {
const {
dom,
store,
subject,
header,
onSaved
} = options;
const button = dom.createElement('button');
button.type = 'button';
button.className = 'social-pane__edit-button';
const icon = dom.createElement('span');
icon.className = 'social-pane__edit-button-icon';
icon.innerHTML = _icons.editIcon;
button.appendChild(icon);
const label = dom.createElement('span');
label.className = 'social-pane__edit-button-label';
label.textContent = 'Edit';
button.appendChild(label);
button.setAttribute('aria-label', 'Edit profile links');
header.style.position = 'relative';
const openDialog = () => {
const initialValues = readProfileLinkValues(store, subject);
const overlay = dom.createElement('div');
overlay.classList.add('social-pane__dialog-backdrop', 'flex-center');
const dialog = dom.createElement('div');
dialog.classList.add('social-pane__dialog');
dialog.setAttribute('role', 'dialog');
dialog.setAttribute('aria-modal', 'true');
dialog.setAttribute('aria-label', 'Edit profile links');
overlay.appendChild(dialog);
const dialogHeader = dom.createElement('div');
dialogHeader.classList.add('social-pane__dialog-header');
dialog.appendChild(dialogHeader);
const title = dom.createElement('h2');
title.className = 'social-pane__dialog-title';
title.textContent = 'Edit profile links';
dialogHeader.appendChild(title);
const closeIconButton = dom.createElement('button');
closeIconButton.type = 'button';
closeIconButton.className = 'social-pane__dialog-close';
closeIconButton.setAttribute('aria-label', 'Close dialog');
closeIconButton.innerHTML = _icons.closeIcon;
dialogHeader.appendChild(closeIconButton);
const form = dom.createElement('form');
form.classList.add('social-pane__dialog-form', 'flex-column');
dialog.appendChild(form);
const errorMessage = dom.createElement('p');
errorMessage.className = 'social-pane__dialog-error';
const fields = new Map();
for (const field of profileLinkFields) {
const fieldWrapper = dom.createElement('label');
fieldWrapper.classList.add('social-pane__dialog-field', 'flex-column');
const fieldLabel = dom.createElement('span');
fieldLabel.className = 'social-pane__dialog-label';
fieldLabel.textContent = field.label;
fieldWrapper.appendChild(fieldLabel);
const input = dom.createElement('input');
input.classList.add('social-pane__dialog-input', 'input');
input.type = 'url';
input.placeholder = field.placeholder;
input.value = initialValues[field.key].split('\n')[0] || '';
fieldWrapper.appendChild(input);
fields.set(field.key, input);
form.appendChild(fieldWrapper);
}
form.appendChild(errorMessage);
const actions = dom.createElement('div');
actions.className = 'social-pane__dialog-actions';
form.appendChild(actions);
const closeButton = dom.createElement('button');
closeButton.type = 'button';
closeButton.className = 'social-pane__dialog-button btn-light';
closeButton.textContent = 'Close';
actions.appendChild(closeButton);
const saveButton = dom.createElement('button');
saveButton.type = 'submit';
saveButton.className = 'social-pane__dialog-button btn-primary';
saveButton.textContent = 'Save Changes';
actions.appendChild(saveButton);
const closeDialog = () => {
overlay.remove();
};
overlay.addEventListener('click', event => {
if (event.target === overlay) closeDialog();
});
closeIconButton.addEventListener('click', closeDialog);
closeButton.addEventListener('click', closeDialog);
overlay.addEventListener('keydown', event => {
if (event.key === 'Escape') closeDialog();
});
form.addEventListener('submit', async event => {
event.preventDefault();
errorMessage.textContent = '';
saveButton.disabled = true;
closeButton.disabled = true;
closeIconButton.disabled = true;
const nextValues = profileLinkFields.reduce((result, field) => {
result[field.key] = fields.get(field.key)?.value || '';
return result;
}, {
homepage: '',
weblog: '',
workplaceHomepage: '',
schoolHomepage: ''
});
try {
await saveProfileLinkValues(store, subject, nextValues);
closeDialog();
onSaved();
} catch (error) {
errorMessage.textContent = error instanceof Error ? error.message : 'Unable to save profile links.';
} finally {
saveButton.disabled = false;
closeButton.disabled = false;
closeIconButton.disabled = false;
}
});
dom.body.appendChild(overlay);
fields.get('homepage')?.focus();
};
button.addEventListener('click', openDialog);
return button;
}