atomicdata-dev / atomic-server

An open source headless CMS / real-time database. Powerful table editor, full-text search, and SDKs for JS / React / Svelte.
https://atomicserver.eu
MIT License
1.01k stars 46 forks source link

Improved CMS capabilities - edit content from a webpage #1000

Open joepio opened 1 day ago

joepio commented 1 day ago

We're now using AtomicServer + front-end frameworks (sveltekit, astro) in production for a couple of customers, and I think there is one common usecase that requires some improvement: see a resource in your deployed website, and decide you want to change it.

Let's dive into how we can improve this:

Browser extension with editing capabilities

When the browser extension is installed, user can hover over a resource to see an edit button. Clicking that opens a resource form overlay. The extension manages the secret of the Agent. The extension queries the dom for some attribute (e.g atomic-subject) and injects a button on hover.

Add alt-click action to compiled front-end

When the user holds the alt or option key, show borders of resources (can be the same atomic-subject) on hover. Clicking opens that resource on atomicdata.dev in edit mode in a new tab.

joepio commented 1 day ago
---
/**
 * A wrapper component that enables alt+click editing functionality for Atomic Data resources.
 * When holding Alt, the wrapped content will show an outline and clicking will redirect to the edit page.
 *
 * @component
 * @example
 * ```astro
 * <ResourceClickWrapper subject="https://example.com/resource">
 *   <SomeContent />
 * </ResourceClickWrapper>
 * ```
 */

type Props = {
  subject: string;
};

const { subject } = Astro.props;
const editLink = `https://atomicdata.dev/app/edit?subject=${subject}`;
---

<div class='block-container' data-edit-link={editLink}>
  <slot />
</div>

<script>
  const blocks = document.querySelectorAll('.block-container');
  const toggleAltClass = (show: boolean) =>
    blocks.forEach(el => el.classList.toggle('alt-active', show));

  document.addEventListener('keydown', (e: KeyboardEvent) =>
    toggleAltClass(e.altKey),
  );
  document.addEventListener('keyup', (e: KeyboardEvent) =>
    toggleAltClass(e.altKey),
  );
  window.addEventListener('blur', () => toggleAltClass(false));
  window.addEventListener('focus', e => toggleAltClass(e.altKey));

  blocks.forEach(el =>
    el.addEventListener('click', (e: MouseEvent) => {
      if (e.altKey)
        window.location.href = el.getAttribute('data-edit-link') || '';
    }),
  );
</script>

<style>
  .block-container.alt-active {
    outline: 2px dashed #666;
    cursor: pointer;
  }
  .block-container.alt-active:hover {
    outline-color: blue;
    background-color: rgba(0, 0, 0, 0.03);
  }
</style>

How about this?

Polleps commented 1 day ago

The extension doesn't need to manage an agent or have any settings or even a UI (Maybe only for usage instructions). When the user clicks the extension icon a script will be injected in the current tab. The script will look for any nodes with a data-atomic-subject attribute, measure it's dimensions using getBoundingClientRect and overlay an element with an outline on top. Then when clicked the user navigates to the subject in the attribute (to [domain]/edit?subject=[subject]). Since we don't edit in the extension it self we don't need an agent, or even use @tomic/lib at all.

We could also publish this script on a cdn and have the developer include it on the page instead. Personally I'd go for the extension because I don't like including dev/admin stuff in a production app. It's also means every visitor will send a request to a cdn for a script that doesn't get used by them att all

Polleps commented 1 day ago
---
/**
 * A wrapper component that enables alt+click editing functionality for Atomic Data resources.
 * When holding Alt, the wrapped content will show an outline and clicking will redirect to the edit page.
 *
 * @component
 * @example
 * ```astro
 * <ResourceClickWrapper subject="https://example.com/resource">
 *   <SomeContent />
 * </ResourceClickWrapper>
 * ```
 */

type Props = {
  subject: string;
};

const { subject } = Astro.props;
const editLink = `https://atomicdata.dev/app/edit?subject=${subject}`;
---

<div class='block-container' data-edit-link={editLink}>
  <slot />
</div>

<script>
  const blocks = document.querySelectorAll('.block-container');
  const toggleAltClass = (show: boolean) =>
    blocks.forEach(el => el.classList.toggle('alt-active', show));

  document.addEventListener('keydown', (e: KeyboardEvent) =>
    toggleAltClass(e.altKey),
  );
  document.addEventListener('keyup', (e: KeyboardEvent) =>
    toggleAltClass(e.altKey),
  );
  window.addEventListener('blur', () => toggleAltClass(false));
  window.addEventListener('focus', e => toggleAltClass(e.altKey));

  blocks.forEach(el =>
    el.addEventListener('click', (e: MouseEvent) => {
      if (e.altKey)
        window.location.href = el.getAttribute('data-edit-link') || '';
    }),
  );
</script>

<style>
  .block-container.alt-active {
    outline: 2px dashed #666;
    cursor: pointer;
  }
  .block-container.alt-active:hover {
    outline-color: blue;
    background-color: rgba(0, 0, 0, 0.03);
  }
</style>

How about this?

This is to specific for astro. We should make it generic so it works with every possible framework

joepio commented 1 day ago

Hmm yeah. Would be better to make it generic.