Refactor project name from "Open Claude Design" to "Open Design" (#1)
* Refactor project name from "Open Claude Design" to "Open Design" - Updated project name in package.json, package-lock.json, and README files. - Changed CLI commands and references from "ocd" to "od". - Adjusted file structure references in documentation and code to reflect new naming conventions. - Enhanced .gitignore to include new runtime data files. - Updated metadata in LICENSE file to match new project name. * Add contributing guidelines in English and Chinese - Introduced CONTRIBUTING.md and CONTRIBUTING.zh-CN.md to provide clear instructions for contributors. - Outlined contribution types, local setup instructions, and merging criteria for skills and design systems. - Enhanced README files to reference the new contributing guidelines.
This commit is contained in:
+210
-21
@@ -9,9 +9,9 @@
|
||||
* When `options.deck` is set we also inject a `postMessage` listener that
|
||||
* lets the host advance / rewind slides without relying on the iframe
|
||||
* having keyboard focus. The host posts:
|
||||
* { type: 'ocd:slide', action: 'next' | 'prev' | 'first' | 'last' | 'go', index?: number }
|
||||
* { type: 'od:slide', action: 'next' | 'prev' | 'first' | 'last' | 'go', index?: number }
|
||||
* and the iframe responds with:
|
||||
* { type: 'ocd:slide-state', active: number, count: number }
|
||||
* { type: 'od:slide-state', active: number, count: number }
|
||||
* after every navigation so the host can render its own counter / dots.
|
||||
*/
|
||||
export function buildSrcdoc(
|
||||
@@ -30,51 +30,240 @@ export function buildSrcdoc(
|
||||
</head>
|
||||
<body>${html}</body>
|
||||
</html>`;
|
||||
if (!options.deck) return wrapped;
|
||||
return injectDeckBridge(wrapped);
|
||||
const withShim = injectSandboxShim(wrapped);
|
||||
if (!options.deck) return withShim;
|
||||
return injectDeckBridge(withShim);
|
||||
}
|
||||
|
||||
// Sandboxed iframes (we use `sandbox="allow-scripts"`) without
|
||||
// `allow-same-origin` raise a SecurityError on first `localStorage` /
|
||||
// `sessionStorage` access. Many freeform-generated decks call
|
||||
// `localStorage.getItem(...)` at the top of their IIFE without a
|
||||
// try/catch — when it throws, the whole script aborts and the deck
|
||||
// becomes a static, unnavigable preview. We install a same-origin
|
||||
// in-memory shim BEFORE any user script runs so those decks degrade
|
||||
// gracefully (position just doesn't persist across reloads).
|
||||
function injectSandboxShim(doc: string): string {
|
||||
const shim = `<script>(function(){
|
||||
function makeStore(){
|
||||
var data = {};
|
||||
var api = {
|
||||
getItem: function(k){ return Object.prototype.hasOwnProperty.call(data, k) ? data[k] : null; },
|
||||
setItem: function(k, v){ data[k] = String(v); },
|
||||
removeItem: function(k){ delete data[k]; },
|
||||
clear: function(){ data = {}; },
|
||||
key: function(i){ return Object.keys(data)[i] || null; }
|
||||
};
|
||||
Object.defineProperty(api, 'length', { get: function(){ return Object.keys(data).length; } });
|
||||
return api;
|
||||
}
|
||||
function tryShim(name){
|
||||
var works = false;
|
||||
try { works = !!window[name] && typeof window[name].getItem === 'function'; void window[name].length; }
|
||||
catch (_) { works = false; }
|
||||
if (works) return;
|
||||
try { Object.defineProperty(window, name, { configurable: true, value: makeStore() }); }
|
||||
catch (_) { try { window[name] = makeStore(); } catch (__) {} }
|
||||
}
|
||||
tryShim('localStorage');
|
||||
tryShim('sessionStorage');
|
||||
})();</script>`;
|
||||
if (/<head[^>]*>/i.test(doc)) return doc.replace(/<head[^>]*>/i, (m) => `${m}${shim}`);
|
||||
if (/<body[^>]*>/i.test(doc)) return doc.replace(/<body[^>]*>/i, (m) => `${m}${shim}`);
|
||||
return shim + doc;
|
||||
}
|
||||
|
||||
// The deck bridge supports three deck conventions found across our skills
|
||||
// and freeform-generated artifacts:
|
||||
// 1. Horizontal scroll decks (simple-deck, guizang-ppt) — slides laid out
|
||||
// side-by-side, navigation = scrollTo({ left }).
|
||||
// 2. Class-toggle decks (deck-framework, freeform pitches) — one slide
|
||||
// carries `.active` or `.is-active`; siblings are display:none. Their
|
||||
// own JS listens for ArrowRight/Left, so we drive them by dispatching
|
||||
// synthetic KeyboardEvents.
|
||||
// 3. Visibility-only decks — no class toggle, slides hidden via inline
|
||||
// style. We fall back to keyboard dispatch + visibility detection.
|
||||
//
|
||||
// All three report `{ active, count }` back to the host so the toolbar can
|
||||
// render a unified counter. A MutationObserver on each `.slide` lets us
|
||||
// catch class changes from the deck's own keyboard handler.
|
||||
//
|
||||
// We also inject a small CSS override that fixes a common authoring
|
||||
// mistake in fixed-canvas decks: a `.stage { display: grid; place-items:
|
||||
// center }` only centers items within their grid cells, but the track
|
||||
// itself stays `start`-aligned, so the 1920x1080 canvas top-lefts at
|
||||
// (0,0) of the stage. Combined with `transform-origin: center center`,
|
||||
// the scaled canvas ends up offset toward the bottom-right of any
|
||||
// preview that's smaller than 1920x1080 — exactly what users see in the
|
||||
// sandbox iframe. `place-content: center` centers the track itself.
|
||||
function injectDeckBridge(doc: string): string {
|
||||
const styleFix = `<style data-od-deck-fix>
|
||||
.stage, .deck-stage, .deck-shell { place-content: center !important; }
|
||||
</style>`;
|
||||
const docWithStyle = /<\/head>/i.test(doc)
|
||||
? doc.replace(/<\/head>/i, styleFix + '</head>')
|
||||
: /<head[^>]*>/i.test(doc)
|
||||
? doc.replace(/<head[^>]*>/i, (m) => m + styleFix)
|
||||
: styleFix + doc;
|
||||
doc = docWithStyle;
|
||||
const script = `<script>(function(){
|
||||
function slides(){ return document.querySelectorAll('.slide'); }
|
||||
function scroller(){
|
||||
if (document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||
if (document.body && document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||
return document.scrollingElement || document.documentElement;
|
||||
}
|
||||
function activeIndex(){
|
||||
return Math.round(scroller().scrollLeft / window.innerWidth);
|
||||
function isScrollDeck(){
|
||||
var sc = scroller();
|
||||
return !!(sc && sc.scrollWidth > sc.clientWidth + 1);
|
||||
}
|
||||
function go(i){
|
||||
function findActiveByClass(list){
|
||||
for (var i=0; i<list.length; i++) {
|
||||
var cl = list[i].classList;
|
||||
if (cl && (cl.contains('is-active') || cl.contains('active') || cl.contains('current'))) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function findActiveByVisibility(list){
|
||||
for (var i=0; i<list.length; i++) {
|
||||
try {
|
||||
var cs = window.getComputedStyle(list[i]);
|
||||
if (cs.display !== 'none' && cs.visibility !== 'hidden' && cs.opacity !== '0') return i;
|
||||
} catch (_) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function activeIndex(list){
|
||||
if (!list || !list.length) return 0;
|
||||
if (isScrollDeck()) {
|
||||
var w = Math.max(1, window.innerWidth);
|
||||
return Math.max(0, Math.min(list.length - 1, Math.round(scroller().scrollLeft / w)));
|
||||
}
|
||||
var byClass = findActiveByClass(list);
|
||||
if (byClass >= 0) return byClass;
|
||||
var byVis = findActiveByVisibility(list);
|
||||
if (byVis >= 0) return byVis;
|
||||
return 0;
|
||||
}
|
||||
function dispatchKey(key){
|
||||
// Bubbles so any listener on window picks it up too. We dispatch on
|
||||
// document only — dispatching on window/body in addition would cause
|
||||
// bubbling to fire the same document-level listener twice.
|
||||
var init = { key: key, code: key, bubbles: true, cancelable: true, composed: true };
|
||||
try {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', init));
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', init));
|
||||
} catch (_) {}
|
||||
}
|
||||
function scrollGo(i){
|
||||
var list = slides();
|
||||
if (!list.length) return;
|
||||
var next = Math.max(0, Math.min(list.length - 1, i));
|
||||
scroller().scrollTo({ left: next * window.innerWidth, behavior: 'smooth' });
|
||||
setTimeout(report, 360);
|
||||
setTimeout(report, 380);
|
||||
}
|
||||
function go(action){
|
||||
var list = slides();
|
||||
if (!list.length) return;
|
||||
if (isScrollDeck()) {
|
||||
var i = activeIndex(list);
|
||||
if (action === 'next') scrollGo(i + 1);
|
||||
else if (action === 'prev') scrollGo(i - 1);
|
||||
else if (action === 'first') scrollGo(0);
|
||||
else if (action === 'last') scrollGo(list.length - 1);
|
||||
return;
|
||||
}
|
||||
if (action === 'next') dispatchKey('ArrowRight');
|
||||
else if (action === 'prev') dispatchKey('ArrowLeft');
|
||||
else if (action === 'first') dispatchKey('Home');
|
||||
else if (action === 'last') dispatchKey('End');
|
||||
setTimeout(report, 280);
|
||||
}
|
||||
function gotoIndex(i){
|
||||
var list = slides();
|
||||
if (!list.length) return;
|
||||
var target = Math.max(0, Math.min(list.length - 1, i));
|
||||
if (isScrollDeck()) { scrollGo(target); return; }
|
||||
var current = activeIndex(list);
|
||||
var diff = target - current;
|
||||
if (!diff) { report(); return; }
|
||||
var key = diff > 0 ? 'ArrowRight' : 'ArrowLeft';
|
||||
var n = Math.abs(diff);
|
||||
for (var k = 0; k < n; k++) dispatchKey(key);
|
||||
setTimeout(report, 320);
|
||||
}
|
||||
function report(){
|
||||
try {
|
||||
var list = slides();
|
||||
window.parent.postMessage({
|
||||
type: 'ocd:slide-state',
|
||||
active: activeIndex(),
|
||||
type: 'od:slide-state',
|
||||
active: activeIndex(list),
|
||||
count: list.length,
|
||||
}, '*');
|
||||
} catch (e) {}
|
||||
}
|
||||
window.addEventListener('message', function(ev){
|
||||
var data = ev && ev.data;
|
||||
if (!data || data.type !== 'ocd:slide') return;
|
||||
var list = slides();
|
||||
var i = activeIndex();
|
||||
if (data.action === 'next') go(i + 1);
|
||||
else if (data.action === 'prev') go(i - 1);
|
||||
else if (data.action === 'first') go(0);
|
||||
else if (data.action === 'last') go(list.length - 1);
|
||||
else if (data.action === 'go' && typeof data.index === 'number') go(data.index);
|
||||
if (!data || data.type !== 'od:slide') return;
|
||||
if (data.action === 'go' && typeof data.index === 'number') gotoIndex(data.index);
|
||||
else go(data.action);
|
||||
});
|
||||
// Report once on load and on every scroll-end so the host stays in sync.
|
||||
window.addEventListener('load', function(){ setTimeout(report, 200); });
|
||||
document.addEventListener('scroll', function(){ clearTimeout(window.__ocdReportT); window.__ocdReportT = setTimeout(report, 120); }, { passive: true, capture: true });
|
||||
document.addEventListener('scroll', function(){
|
||||
clearTimeout(window.__odReportT);
|
||||
window.__odReportT = setTimeout(report, 120);
|
||||
}, { passive: true, capture: true });
|
||||
// Nudge the deck's own fit/resize listener after layout settles. Fixed-canvas
|
||||
// decks (e.g. ".canvas { width: 1920px }" + "transform: scale(...)") compute
|
||||
// their scale on first run, which fires when the iframe is still 0x0 in
|
||||
// sandboxed previews — the deck's fit() then resolves to scale(0) / scale(1)
|
||||
// and never recovers. Re-firing 'resize' lets the deck recompute, and a
|
||||
// ResizeObserver picks up later layout settles (zoom toggle, sidebar drag).
|
||||
function nudgeResize(){
|
||||
try { window.dispatchEvent(new Event('resize')); }
|
||||
catch (_) {}
|
||||
}
|
||||
// Aggressively nudge during the first second so the deck catches the
|
||||
// iframe's first non-zero size; bail out early once the iframe reports a
|
||||
// real width. Without this loop, fixed-canvas decks render at scale(0).
|
||||
function chaseFirstLayout(){
|
||||
var attempts = 0;
|
||||
function tick(){
|
||||
attempts += 1;
|
||||
var w = window.innerWidth;
|
||||
nudgeResize();
|
||||
if (w > 0 && attempts >= 2) return; // one extra nudge after first non-zero
|
||||
if (attempts < 30) setTimeout(tick, 50);
|
||||
}
|
||||
tick();
|
||||
}
|
||||
if (document.readyState === 'complete') chaseFirstLayout();
|
||||
else window.addEventListener('load', chaseFirstLayout);
|
||||
// Re-nudge whenever the iframe itself is resized by the host (e.g.
|
||||
// user toggles zoom, resizes the chat sidebar, exits Present).
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
try {
|
||||
var ro = new ResizeObserver(function(){ nudgeResize(); });
|
||||
ro.observe(document.documentElement);
|
||||
} catch (_) {}
|
||||
}
|
||||
// For class-toggle decks the deck's own keyboard handler updates classes
|
||||
// on the slide elements; an attribute observer translates that into the
|
||||
// host counter without depending on scroll events.
|
||||
function observeSlides(){
|
||||
var list = slides();
|
||||
if (!list.length) { setTimeout(observeSlides, 150); return; }
|
||||
try {
|
||||
var mo = new MutationObserver(function(){
|
||||
clearTimeout(window.__odReportT2);
|
||||
window.__odReportT2 = setTimeout(report, 60);
|
||||
});
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
mo.observe(list[i], { attributes: true, attributeFilter: ['class', 'style', 'hidden', 'aria-hidden'] });
|
||||
}
|
||||
} catch (e) {}
|
||||
setTimeout(report, 100);
|
||||
}
|
||||
observeSlides();
|
||||
})();</script>`;
|
||||
if (/<\/body>/i.test(doc)) return doc.replace(/<\/body>/i, `${script}</body>`);
|
||||
return doc + script;
|
||||
|
||||
Reference in New Issue
Block a user