Initial import: open-design source for helix-mind.ai distribution
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m6s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 12s
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m6s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 12s
This repository contains the open-design daemon CLI source code, built and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use by the HelixMind /design slash command. Licenses: Apache-2.0 (root) + MIT (skills/*)
This commit is contained in:
204
apps/web/src/edit-mode/bridge.ts
Normal file
204
apps/web/src/edit-mode/bridge.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
export const MANUAL_EDIT_DISCOVERY_SELECTOR = 'main, nav, section, article, header, footer, div, h1, h2, h3, p, a, button, img, strong, span';
|
||||
export const MANUAL_EDIT_SOURCE_PATH_ATTR = 'data-od-source-path';
|
||||
export const MANUAL_EDIT_HOST_NODE_SELECTOR = [
|
||||
'[data-od-sandbox-shim]',
|
||||
'[data-od-deck-bridge]',
|
||||
'[data-od-comment-bridge]',
|
||||
'[data-od-edit-bridge]',
|
||||
'[data-od-comment-bridge-style]',
|
||||
'[data-od-edit-bridge-style]',
|
||||
'[data-od-deck-fix]',
|
||||
].join(',');
|
||||
|
||||
export function manualEditDomPathForElement(el: Element): string {
|
||||
const parts: number[] = [];
|
||||
let node: Element | null = el;
|
||||
while (node && node !== node.ownerDocument.body) {
|
||||
const parentEl: Element | null = node.parentElement;
|
||||
if (!parentEl) break;
|
||||
const children = Array.from(parentEl.children).filter((child) => !isManualEditHostNode(child));
|
||||
parts.unshift(children.indexOf(node));
|
||||
node = parentEl;
|
||||
}
|
||||
return parts.length ? `path-${parts.join('-')}` : '';
|
||||
}
|
||||
|
||||
export function isManualEditHostNode(el: Element): boolean {
|
||||
return el.matches(MANUAL_EDIT_HOST_NODE_SELECTOR);
|
||||
}
|
||||
|
||||
export function manualEditStableIdForElement(el: Element): string {
|
||||
const explicit = el.getAttribute('data-od-id');
|
||||
if (explicit) return explicit;
|
||||
const generated = el.getAttribute(MANUAL_EDIT_SOURCE_PATH_ATTR) || el.getAttribute('data-od-runtime-id') || manualEditDomPathForElement(el);
|
||||
if (generated) el.setAttribute('data-od-runtime-id', generated);
|
||||
return generated || 'unknown';
|
||||
}
|
||||
|
||||
export function isMeaningfulManualEditElement(el: Element, rect: Pick<DOMRect, 'width' | 'height'>): boolean {
|
||||
return isSourceMappableManualEditElement(el) && el.matches(MANUAL_EDIT_DISCOVERY_SELECTOR) && rect.width >= 4 && rect.height >= 4;
|
||||
}
|
||||
|
||||
export function isSourceMappableManualEditElement(el: Element): boolean {
|
||||
return el.hasAttribute('data-od-id') || el.hasAttribute(MANUAL_EDIT_SOURCE_PATH_ATTR);
|
||||
}
|
||||
|
||||
export function buildManualEditBridge(enabled: boolean): string {
|
||||
return `<script data-od-edit-bridge>(function(){
|
||||
var enabled = ${JSON.stringify(enabled)};
|
||||
var discoverySelector = ${JSON.stringify(MANUAL_EDIT_DISCOVERY_SELECTOR)};
|
||||
var hostNodeSelector = ${JSON.stringify(MANUAL_EDIT_HOST_NODE_SELECTOR)};
|
||||
var sourcePathAttr = ${JSON.stringify(MANUAL_EDIT_SOURCE_PATH_ATTR)};
|
||||
var styleProps = ['color','backgroundColor','fontSize','fontWeight','textAlign','padding','margin','borderRadius','border','width','minHeight'];
|
||||
function isHostNode(el){
|
||||
return !!(el && el.matches && el.matches(hostNodeSelector));
|
||||
}
|
||||
function domPath(el){
|
||||
var parts = [];
|
||||
var node = el;
|
||||
while (node && node !== document.body) {
|
||||
var parent = node.parentElement;
|
||||
if (!parent) break;
|
||||
var children = Array.prototype.slice.call(parent.children).filter(function(child){ return !isHostNode(child); });
|
||||
parts.unshift(children.indexOf(node));
|
||||
node = parent;
|
||||
}
|
||||
return parts.length ? 'path-' + parts.join('-') : '';
|
||||
}
|
||||
function stableId(el){
|
||||
var explicit = el.getAttribute('data-od-id');
|
||||
if (explicit) return explicit;
|
||||
var generated = el.getAttribute(sourcePathAttr) || el.getAttribute('data-od-runtime-id') || domPath(el);
|
||||
if (generated) el.setAttribute('data-od-runtime-id', generated);
|
||||
return generated || 'unknown';
|
||||
}
|
||||
function isSourceMappable(el){
|
||||
return !!(el && el.hasAttribute && (el.hasAttribute('data-od-id') || el.hasAttribute(sourcePathAttr)));
|
||||
}
|
||||
function isDiscoveryTarget(el){
|
||||
return !!(el && el.matches && el.matches(discoverySelector));
|
||||
}
|
||||
function isPrimaryTarget(el){
|
||||
if (!el || !el.hasAttribute) return false;
|
||||
if (el.hasAttribute('data-od-id') || el.hasAttribute('data-od-edit')) return true;
|
||||
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
||||
return tag === 'a' || tag === 'button';
|
||||
}
|
||||
function inferKind(el){
|
||||
var explicit = el.getAttribute('data-od-edit');
|
||||
if (explicit) return explicit;
|
||||
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
||||
if (tag === 'a') return 'link';
|
||||
if (tag === 'img') return 'image';
|
||||
if (['section','main','nav','div','article','header','footer'].indexOf(tag) >= 0) return 'container';
|
||||
return 'text';
|
||||
}
|
||||
function labelFor(el, id, kind){
|
||||
var explicit = el.getAttribute('data-od-label');
|
||||
if (explicit) return explicit;
|
||||
var tag = el.tagName ? el.tagName.toLowerCase() : 'element';
|
||||
var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
|
||||
if (text) return text.slice(0, 42);
|
||||
if (kind === 'image') return el.getAttribute('alt') || id;
|
||||
return tag + ' #' + id;
|
||||
}
|
||||
function attrsFor(el){
|
||||
var attrs = {};
|
||||
for (var i = 0; i < el.attributes.length; i++) {
|
||||
var attr = el.attributes[i];
|
||||
if (!attr || attr.name.indexOf('data-od-runtime') === 0) continue;
|
||||
attrs[attr.name] = attr.value;
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
function stylesFor(el){
|
||||
var computed = window.getComputedStyle(el);
|
||||
var styles = {};
|
||||
styleProps.forEach(function(prop){ styles[prop] = el.style[prop] || computed[prop] || ''; });
|
||||
return styles;
|
||||
}
|
||||
function targetFrom(el, includeOuterHtml){
|
||||
var rect = el.getBoundingClientRect();
|
||||
var kind = inferKind(el);
|
||||
var id = stableId(el);
|
||||
var fields = {};
|
||||
if (kind === 'link') {
|
||||
fields.text = (el.textContent || '').trim();
|
||||
fields.href = el.getAttribute('href') || '';
|
||||
} else if (kind === 'image') {
|
||||
fields.src = el.getAttribute('src') || '';
|
||||
fields.alt = el.getAttribute('alt') || '';
|
||||
} else {
|
||||
fields.text = (el.textContent || '').trim();
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
kind: kind,
|
||||
label: labelFor(el, id, kind),
|
||||
tagName: el.tagName ? el.tagName.toLowerCase() : 'element',
|
||||
className: typeof el.className === 'string' ? el.className : '',
|
||||
text: (el.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 180),
|
||||
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
|
||||
fields: fields,
|
||||
attributes: attrsFor(el),
|
||||
styles: stylesFor(el),
|
||||
outerHtml: includeOuterHtml ? (el.outerHTML || '').replace(/\\sdata-od-runtime-id="[^"]*"/g, '').replace(/\\sdata-od-source-path="[^"]*"/g, '') : ''
|
||||
};
|
||||
}
|
||||
function allTargets(){
|
||||
var nodes = document.body ? document.body.querySelectorAll(discoverySelector) : [];
|
||||
var targets = [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var rect = nodes[i].getBoundingClientRect();
|
||||
if (rect.width < 4 || rect.height < 4) continue;
|
||||
if (!isSourceMappable(nodes[i])) continue;
|
||||
targets.push(targetFrom(nodes[i], false));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
function postTargets(){
|
||||
if (!enabled) return;
|
||||
window.parent.postMessage({ type: 'od-edit-targets', targets: allTargets() }, '*');
|
||||
}
|
||||
function closestTarget(event){
|
||||
var el = event.target;
|
||||
var fallback = null;
|
||||
while (el && el !== document.documentElement) {
|
||||
if (el !== document.body && el !== document.documentElement && isSourceMappable(el) && isDiscoveryTarget(el)) {
|
||||
if (isPrimaryTarget(el)) return el;
|
||||
if (!fallback) fallback = el;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
window.addEventListener('message', function(ev){
|
||||
if (!ev.data || ev.data.type !== 'od-edit-mode') return;
|
||||
enabled = !!ev.data.enabled;
|
||||
document.documentElement.toggleAttribute('data-od-edit-mode', enabled);
|
||||
if (enabled) setTimeout(postTargets, 0);
|
||||
});
|
||||
document.addEventListener('click', function(ev){
|
||||
if (!enabled) return;
|
||||
var el = closestTarget(ev);
|
||||
if (!el) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.parent.postMessage({ type: 'od-edit-select', target: targetFrom(el, true) }, '*');
|
||||
}, true);
|
||||
window.addEventListener('resize', postTargets);
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', postTargets);
|
||||
else setTimeout(postTargets, 0);
|
||||
document.documentElement.toggleAttribute('data-od-edit-mode', enabled);
|
||||
})();</script>`;
|
||||
}
|
||||
|
||||
export function buildManualEditBridgeStyle(): string {
|
||||
return `<style data-od-edit-bridge-style>
|
||||
html[data-od-edit-mode] body * { cursor: pointer !important; }
|
||||
html[data-od-edit-mode] [data-od-id],
|
||||
html[data-od-edit-mode] [data-od-runtime-id] { outline: 1px dashed rgba(37, 99, 235, 0.35); outline-offset: 3px; }
|
||||
html[data-od-edit-mode] [data-od-id]:hover,
|
||||
html[data-od-edit-mode] [data-od-runtime-id]:hover { outline: 2px solid #2563eb; }
|
||||
</style>`;
|
||||
}
|
||||
Reference in New Issue
Block a user