3 - Make plugin less basic
Upgrade your JavaScript with a Web Component
- /tools/tag-gen/tag-gen.js (JavaScript)
import DA_SDK from 'https://da.live/nx/utils/sdk.js';
import { LitElement, html, nothing } from 'da-lit';
import { loadPageTags, loadGenTags, savePageTags } from './utils.js';
// Super Lite components
import 'https://da.live/nx/public/sl/components.js';
// Application styles
import loadStyle from '../../scripts/utils/styles.js';
const styles = await loadStyle(import.meta.url);
class ADLTagGen extends LitElement {
static properties = {
path: { attribute: false },
token: { attribute: false },
_pageTags: { state: true },
_genTags: { state: true },
_status: { state: true },
};
connectedCallback() {
super.connectedCallback();
this.shadowRoot.adoptedStyleSheets = [styles];
this.getPageTags();
}
async getPageTags() {
this._pageTags = await loadPageTags(this.path, this.token);
this._status = undefined;
}
async generateTags() {
this._status = 'Generating tags...';
this._genTags = await loadGenTags(this.path, this.token);
this._status = undefined;
}
async updateTags() {
this._status = 'Updating page...';
const { message, type } = await savePageTags(this.path, this.token, this._genTags);
if (type === 'success') {
this._pageTags = [...this._genTags];
this._genTags = undefined;
this._status = undefined;
} else {
this._status = message;
}
}
get title() {
return this._genTags ? 'Generated tags' : 'Current tags';
}
get tags() {
return this._genTags || this._pageTags;
}
renderTags() {
if (!this.tags) return nothing;
return html`
<p class="title ${this._genTags ? 'generated' : ''}">${this.title}</p>
<ul>${this.tags.map((tag) => html`<li>${tag}</li>`)}</ul>
<div class="action-area">
${this.title === 'Current tags'
? html`<button class="btn-gradient" @click=${this.generateTags}>
<svg><use href="https://main--summit-labs--aemsites.aem.live/tools/tag-gen/tag-gen.svg#tag-gen" /></svg> Generate tags</button>`
: html`<sl-button @click=${this.updateTags}>Save tags</sl-button>`}
</div>
`;
}
renderStatus() {
return html`
<div class="status-container">
<svg><use href="https://main--summit-labs--aemsites.aem.live/tools/tag-gen/tag-gen.svg#tag-gen" /></svg>
<p class="status">${this._status}</p>
</div>
`;
}
render() {
if (!(this._genTags || this._pageTags || this._status)) return nothing;
return html`
${this._status !== undefined ? this.renderStatus() : this.renderTags()}
`;
}
}
customElements.define('adl-tag-gen', ADLTagGen);
(async function init() {
const { context, token } = await DA_SDK;
const { org, repo, path } = context;
const cmp = document.createElement('adl-tag-gen');
cmp.path = `/${org}/${repo}${path}`;
cmp.token = token;
document.body.append(cmp);
}());
Observations
- DA SDK - We are importing the SDK which will securely give us our IMS token, context over where we are (org, site, path, etc.), and a few actions we can use to send content to the editor.
- Lit - We are importing our own version of Lit. This is an ESM / buildless-friendly version with a few extra utilities rolled in.
- Super Lite - A lightweight experimental UI library with DOM API compatibility with plain HTML.
- utils.js - For easier maintenance and testability, we separate the bulk of our logic into a separate utils file. This allows our web component to be mostly focused on presentation.
- Styling - We use a lightweight polyfill to load CSS as a scoped style sheet based on the name of our current file.
Make utils.js
Let's add our utility functions...
- /tools/tag-gen/utils.js (JavaScript)
function createMetadataBlock() {
const metadata = document.createElement('div');
metadata.className = 'metadata';
return metadata;
}
function createTagRow(tags) {
const tagRow = document.createElement('div');
const tagKey = document.createElement('div');
tagKey.textContent = 'tags';
const tagVal = document.createElement('div');
tagVal.textContent = tags.join(', ');
tagRow.append(tagKey, tagVal);
return tagRow;
}
function getOpts(token, method = 'GET') {
return {
method,
headers: {
Authorization: `Bearer ${token}`,
},
};
}
async function fetchDoc(path, token) {
const opts = getOpts(token);
const resp = await fetch(`https://admin.da.live/source${path}.html`, opts);
if (!resp.ok) return { message: 'Could not fetch doc.', status: resp.status };
const html = await resp.text();
return { html };
}
async function saveDoc(path, token, doc) {
// Create the body
const body = new FormData();
const html = doc.body.outerHTML;
const data = new Blob([html], { type: 'text/html' });
body.append('data', data);
// Setup options
const opts = getOpts(token, 'POST');
opts.body = body;
const resp = await fetch(`https://admin.da.live/source${path}.html`, opts);
if (!resp.ok) return { message: 'Could not save.', status: resp.status, type: 'error' };
return { message: 'Successfully saved.', status: resp.status, type: 'success' };
}
const getMetadata = (el) => [...el.childNodes].reduce((rdx, row) => {
if (row.children) {
const key = row.children[0].textContent.trim().toLowerCase();
const content = row.children[1];
const text = content.textContent.trim().toLowerCase();
if (key && text) rdx[key] = { text };
}
return rdx;
}, {});
export async function loadGenTags(path, token) {
const { html } = await fetchDoc(path, token);
const baseOpts = getOpts(token, 'POST');
const opts = { ...baseOpts, body: JSON.stringify({ html }) };
const resp = await fetch(`https://da-etc.adobeaem.workers.dev/tags`, opts);
if (!resp.ok) return [];
const { tags } = await resp.json();
return tags;
}
export async function loadPageTags(path, token) {
const { html } = await fetchDoc(path, token);
if (!html) return [];
const doc = new DOMParser().parseFromString(html, 'text/html');
const metaEl = doc.querySelector('.metadata');
if (metaEl) {
const { tags } = getMetadata(metaEl);
if (tags) {
return tags.text.split(',').map((tag) => tag.trim().toLowerCase());
}
}
return [];
}
export async function savePageTags(path, token, tags) {
// Build the tag row elements
const tagsRow = createTagRow(tags);
// Always get a fresh doc
const { html } = await fetchDoc(path, token);
const doc = new DOMParser().parseFromString(html, 'text/html');
// Re-use existing metadata block if possible
const metaEl = doc.querySelector('.metadata');
if (metaEl) {
const metaRows = metaEl.querySelectorAll(':scope > div');
const foundRow = [...metaRows].find((row) => {
const text = row.children[0].textContent;
return text === 'tags';
});
if (foundRow) {
foundRow.parentElement.replaceChild(tagsRow, foundRow);
} else {
metaEl.append(tagsRow);
}
} else {
// Make net-new metadata block
const newMetaEl = createMetadataBlock();
newMetaEl.append(tagsRow);
doc.body.querySelector('main > div:last-child').append(newMetaEl);
}
return saveDoc(path, token, doc);
}
Observations
There are three main functions:
- loadPageTags - This will fetch the page from DA Admin, parse the DOM, and give back the tags in a simple text array.
- loadGenTags - This function will send our HTML to a simple worker. The worker will convert the HTML into plain text, ask ChatGPT (GPT-5 Nano) to give 5-10 keywords that summarize the document, and send it back to us.
- savePageTags - This function will re-fetch the HTML, find the metadata block with a tags row or create a new one, and save the tags from our worker.
Make a fancy icon
Note: you may need to ask your code editor to open the SVG in code or text mode.
- /tools/tag-gen/tag-gen.svg (markup)
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" id="tag-gen">
<path d="M10.0006 5.26036C10.2313 5.25223 10.456 5.33423 10.6273 5.48898C10.9576 5.85403 10.9576 6.41 10.6273 6.77505C10.4579 6.93356 10.2324 7.01813 10.0006 7.01009C9.76426 7.01957 9.53472 6.92974 9.36759 6.76234C9.20552 6.59444 9.11843 6.36802 9.12622 6.13479C9.11384 5.89982 9.19581 5.66964 9.35392 5.49539C9.5275 5.33065 9.76179 5.24551 10.0006 5.26036Z" fill="currentColor"/>
<path d="M10 15.0645C9.58594 15.0645 9.25 14.7285 9.25 14.3145V9.47949C9.25 9.06543 9.58594 8.72949 10 8.72949C10.4141 8.72949 10.75 9.06543 10.75 9.47949V14.3145C10.75 14.7285 10.4141 15.0645 10 15.0645Z" fill="currentColor"/>
<path d="M9.99998 18.75C5.6699 18.75 2.03514 15.6504 1.3574 11.3789C1.3408 11.2705 1.32517 11.1611 1.31248 11.0518C1.26463 10.6396 1.55857 10.2676 1.96971 10.2188C2.38573 10.1758 2.75389 10.4639 2.80272 10.876C2.81346 10.9658 2.82518 11.0557 2.83983 11.1445C3.40038 14.6826 6.4121 17.25 9.99999 17.25C13.998 17.25 17.25 13.998 17.25 10C17.25 6.05664 14.043 2.80469 10.1025 2.75098L9.99999 2.75C9.8164 2.75 9.63476 2.75684 9.45409 2.76953C9.04784 2.80469 8.68358 2.48633 8.65429 2.07324C8.62597 1.66015 8.93749 1.30176 9.35058 1.27344C9.56445 1.25879 9.78124 1.25 9.99999 1.25L10.1318 1.25098C14.8799 1.31641 18.75 5.24024 18.75 10C18.75 14.8252 14.8252 18.75 9.99998 18.75Z" fill="currentColor"/>
<path d="M1.04746 8.79785C0.940037 8.79785 0.832617 8.77051 0.734957 8.71435C0.499607 8.57861 0.378997 8.30615 0.436617 8.04101L0.562597 7.45703L0.161717 7.01465C-0.0204129 6.81348 -0.0521529 6.51758 0.0840771 6.28272C0.218847 6.04737 0.490327 5.92383 0.756927 5.98438L1.34043 6.11036L1.78281 5.70948C1.98349 5.52686 2.28037 5.49415 2.51474 5.63184C2.75009 5.76758 2.8707 6.03955 2.81308 6.30469L2.6871 6.8877L3.08798 7.33008C3.2706 7.53125 3.30234 7.82715 3.16659 8.0625C3.03036 8.29736 2.75936 8.41846 2.49325 8.36084L1.90975 8.23486L1.46737 8.63574C1.34921 8.74267 1.19883 8.79785 1.04746 8.79785Z" fill="currentColor"/>
<path d="M4.15589 5.5835C4.04847 5.5835 3.94105 5.55616 3.84339 5.5C3.60804 5.36426 3.48743 5.0918 3.54505 4.82666L3.84632 3.43115L2.88782 2.37353C2.7052 2.17236 2.67346 1.87646 2.8097 1.64111C2.94495 1.40576 3.21449 1.28222 3.48304 1.34277L4.87806 1.64453L5.93568 0.685551C6.13734 0.502441 6.43324 0.470221 6.6681 0.607431C6.90345 0.743171 7.02406 1.01514 6.96644 1.28077L6.66468 2.67579L7.62366 3.73341C7.80628 3.93458 7.83802 4.23048 7.70227 4.46583C7.56653 4.70069 7.29553 4.82179 7.02893 4.76417L5.63342 4.4629L4.5758 5.4214C4.45764 5.52833 4.30726 5.5835 4.15589 5.5835Z" fill="currentColor"/>
</svg>
Test it
-
Navigate back to your open page:
https://da.live/edit?ref=local#/{ORG}/{SITE}/index -
At the bottom of the page, there should be an existing metadata block.
-
If there's not one, create and populate a few mock tags:
hello, world, goodnight, moon...
-
Right click somewhere inside your library plugin and select Reload Frame. It should populate with the tags you placed on your page.
-
Click Generate tags to fetch new tags based on the content of your page.
-
After 4-8 seconds, new tags should appear in your plugin.
-
Click Save tags to update your document. At the bottom of your page, you will see your updated tags in the metadata block.
Once your tags are on your page, preview the page.
Paper airplane > Preview.
Success!
Congratulations. You have combined the power of GenAI and Edge Delivery save your creators and authors time.