4 - Build your audit app

Write your HTML

  • /tools/tag-audit/tag-audit.html (markup)
<!DOCTYPE html>
<html>
  <head>
    <title>Tag Audit</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link rel="icon" href="data:,">
    <script type="importmap">
      { "imports": { "da-lit": "/deps/lit/dist/index.js" } }
    </script>
    <!-- Import DA App SDK -->
    <script src="https://da.live/nx/utils/sdk.js" type="module"></script>
    <!-- Project App Logic -->
    <script src="/tools/tag-audit/tag-audit.js" type="module"></script>
  </head>
  <body>
  </body>
</html>

Write some basic JavaScript

  • /tools/tag-audit/tag-audit.js (JavaScript)
import DA_SDK from 'https://da.live/nx/utils/sdk.js';

(async function init() {
  const { context, token } = await DA_SDK;
  const { org, repo, path } = context;

  console.log(org, repo, path, token);

  // const cmp = document.createElement('adl-tag-audit');
  // cmp.path = `/${org}/${repo}`;
  // cmp.token = token;

  // document.body.append(cmp);
}());

Write a few styles

  • /tools/tag-audit/tag-audit.css (css)
:host {
  display: block;
  max-width: var(--grid-container-width);
  margin: 32px auto;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;

  .page-list {
    display: none;
  }

  .title-area {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .title {
    line-height: 1;
    font-weight: 700;
    margin: 0;
  }

  .title-num-actions {
    display: flex;
    gap: 8px;
  }

  .count {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 28px;
    min-width: 28px;
    border: 2px solid var(--s2-blue-200);
    padding: 0 12px;
    border-radius: 15px;
    background-color: var(--s2-blue-200);
  }

  .tag-item {
    padding: 18px;
    border-top: 1px solid #efefef;
    border-bottom: 1px solid #efefef;
    margin: -1px 0;
    transition: background-color 0.25s ease-in-out;

    a {
      padding: 18px 0;
      line-height: 1;
    }

    &.is-open {
      .page-list {
        display: initial;
      }

      .title-area {
        padding-bottom: 18px;
        border-bottom: 1px solid #efefef;
        margin-bottom: 18px;
      }
    }

    &:hover {
      background-color: var(--s2-blue-100);
    }
  }

  &.tags-list {
    border: 2px solid #efefef;
    border-radius: 16px;
    overflow: hidden;
  }
}

Run & test your application

https://da.live/app/{ORG}/{SITE}/tools/tag-audit/tag-audit?ref=local

You should see a console.log of the DA SDK properties.

Update your JavaScript

Main UI

  • /tools/tag-audit/tag-audit.js (JavaScript)
import DA_SDK from 'https://da.live/nx/utils/sdk.js';
import { LitElement, html, nothing } from 'da-lit';
import loadTags 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 ADLTagAudit extends LitElement {
  static properties = {
    path: { attribute: false },
    token: { attribute: false },
    _status: { state: true },
    _tags: { state: true },
  };

  connectedCallback() {
    super.connectedCallback();
    this.shadowRoot.adoptedStyleSheets = [styles];
    this.getTags();
  }

  async getTags() {
    const setStatus = (message) => {
      this._status = message;
    };

    this._tags = await loadTags(this.path, this.token, setStatus);
    console.log(this._tags);
    this._status = undefined;
  }

  toggleOpen(tag) {
    tag.open = !tag.open;
    this.requestUpdate();
  }

  renderPages(pages) {
    return html`
      <ul class="page-list">
        ${pages.map((page) => html`
          <li>
            <a href="https://da.live/edit#${page.uiPath}" target="_blank">${page.uiPath}</a>
          </li>`)}
      </ul>`
  }

  renderTag(tag) {
    const noun = tag.pages.length === 1 ? 'Page' : 'Pages';

    return html`
      <li class="tag-item ${tag.open ? 'is-open' : ''}">
          <div class="title-area">
          <span class="title">${tag.name}</span>
          <div class="title-num-actions">
              <span class="count">${tag.pages.length} ${noun}</span>
              <sl-button
              class="primary outline"
              @click=${() => this.toggleOpen(tag)}>${tag.open ? 'Close' : 'View pages'}</sl-button>
          </div>
          </div>
          ${this.renderPages(tag.pages)}
      </li>`;
  }

  renderTags() {
    if (!this._tags) return nothing;
    return html`
      <ul class="tags-list">
        ${this._tags.map((tag) => this.renderTag(tag))}
      </ul>
    `;
  }

  renderStatus() {
    return html`<p class="status">${this._status}</p>`;
  }

  render() {
    return html`
      <h1>Tag Audit</h1>
      ${this._status ? this.renderStatus() : this.renderTags()}
    `;
  }
}

customElements.define('adl-tag-audit', ADLTagAudit);

(async function init() {
  const { context, token } = await DA_SDK;
  const { org, repo, path } = context;

  const cmp = document.createElement('adl-tag-audit');
  cmp.path = `/${org}/${repo}`;
  cmp.token = token;

  document.body.append(cmp);
}());

Utils

  • /tools/tag-audit/utils.js (JavaScript)
import { crawl } from 'https://da.live/nx/public/utils/tree.js';

function getOpts(token, method = 'GET') {
  return {
    method,
    headers: { Authorization: `Bearer ${token}` },
  };
}

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;
}, {});

async function fetchDoc(path, token) {
  const opts = getOpts(token);
  const resp = await fetch(`https://admin.da.live/source${path}`, opts);
  if (!resp.ok) return { message: 'Could not fetch doc.', status: resp.status };
  const html = await resp.text();
  return { html };
}

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 default async function loadTags(path, token, setStatus) {
  const callback = async (item) => {
    if (item.ext !== 'html') return;
    item.uiPath = item.path.replace('.html', '');
    setStatus(`Loading ${item.uiPath}`);
    item.tags = await loadPageTags(item.path, token);
  };

  const { results } = crawl({ path, callback, throttle: 10 });
  // Wait for results to finish
  const fullfilled = await results;

  // Reduce down to our desired format
  return fullfilled.reduce((acc, item) => {
    if (item.tags?.length > 0) {
      // De-dupe tags
      const uniqueTags = [...new Set(item.tags)];

      // loop through each page tag
      uniqueTags.forEach((pageTag) => {
        // find the tag in the existing accumulator
        const foundTag = acc.find((tag) => tag.name === pageTag);

        if (foundTag) {
          foundTag.pages.push(item);
        } else {
          const newTag = { name: pageTag, pages: [item] };
          acc.push(newTag);
        }
      });
    }
    return acc;
  }, []);
}

Test again

Refresh https://da.live/app/{ORG}/{SITE}/tools/tag-audit/tag-audit?ref=local

You should see a list of tags that can expand to show the associated pages.

Add to your project

Create a new "apps" tab in your site config sheet.

Add a new row:

title description image path ref
Tags Audit Audit page tags /app/{ORG}/{SITE}/tools/tag-audit/tag-audit?ref=local local

Save your config:

Visit your site apps

https://da.live/apps?ref=local#/{ORG}/{SITE}