Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 5 additions & 116 deletions docs/public/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import { createWorkerCompiler } from '/gh-aw/wasm/compiler-loader.js';
import { frontmatterHoverTooltip } from './hover-tooltips.js';

// ---------------------------------------------------------------
// Sample workflow registry (fetched from GitHub on demand)
// Sample workflow registry
// ---------------------------------------------------------------
const AGENTICS_RAW = 'https://raw.githubusercontent.com/githubnext/agentics/main/workflows';

const SAMPLES = {
'hello-world': {
Expand All @@ -33,80 +32,15 @@ engine: copilot
Say hello to the world! Check the current date and time, and greet the user warmly.
`,
},
'issue-triage': {
label: 'Issue Triage',
url: `${AGENTICS_RAW}/issue-triage.md`,
},
'ci-doctor': {
label: 'CI Doctor',
url: `${AGENTICS_RAW}/ci-doctor.md`,
},
'contribution-check': {
label: 'Contribution Guidelines Checker',
url: `${AGENTICS_RAW}/contribution-guidelines-checker.md`,
},
'daily-repo-status': {
label: 'Daily Repo Status',
url: `${AGENTICS_RAW}/daily-repo-status.md`,
},
};

// Cache for fetched content (keyed by URL)
const contentCache = new Map();

// Allowlist of trusted origins for fetching workflow content
const ALLOWED_FETCH_ORIGINS = new Set([
'https://raw.githubusercontent.com',
'https://github.com',
]);

const DEFAULT_CONTENT = SAMPLES['hello-world'].content;

// ---------------------------------------------------------------
// GitHub URL helpers
// ---------------------------------------------------------------

/** Convert github.com blob/tree URLs to raw.githubusercontent.com */
function toRawGitHubUrl(url) {
// https://github.com/{owner}/{repo}/blob/{ref}/{path}
const blobMatch = url.match(
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/
);
if (blobMatch) {
const [, owner, repo, ref, path] = blobMatch;
return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`;
}
return url;
}

/** Fetch markdown content from a URL (with cache) */
async function fetchContent(url) {
const rawUrl = toRawGitHubUrl(url);
let parsedUrl;
try {
parsedUrl = new URL(rawUrl);
} catch {
throw new Error(`Invalid URL: ${rawUrl}`);
}
if (!ALLOWED_FETCH_ORIGINS.has(parsedUrl.origin)) {
throw new Error(`Fetch not allowed from: ${parsedUrl.origin}`);
}
if (contentCache.has(rawUrl)) return contentCache.get(rawUrl);
const resp = await fetch(rawUrl);
if (!resp.ok) throw new Error(`Failed to fetch ${rawUrl}: ${resp.status}`);
const text = await resp.text();
contentCache.set(rawUrl, text);
return text;
}

// ---------------------------------------------------------------
// Hash-based deep linking
//
// Supported formats:
// #hello-world — built-in sample key
// #issue-triage — built-in sample key
// #https://raw.github... — arbitrary raw URL
// #https://github.com/o/r/blob/main/file.md — auto-converted
// ---------------------------------------------------------------

function getHashValue() {
Expand Down Expand Up @@ -245,68 +179,24 @@ function setEditorContent(text) {
}

/** Load a built-in sample by key */
async function loadSample(key) {
function loadSample(key) {
const sample = SAMPLES[key];
if (!sample) return;

// Sync dropdown
sampleSelect.value = key;
setHashQuietly(key);

if (sample.content) {
setEditorContent(sample.content);
return;
}

// Fetch from URL
setStatus('compiling', 'Fetching...');
try {
const text = await fetchContent(sample.url);
sample.content = text; // cache on the sample object too
setEditorContent(text);
} catch (err) {
setStatus('error', 'Fetch failed');
errorText.textContent = err.message;
errorBanner.classList.remove('d-none');
}
}

/** Load content from an arbitrary URL (deep-link) */
async function loadFromUrl(url) {
// Set dropdown to show it's a custom URL
if (!sampleSelect.querySelector('option[value="__url"]')) {
const opt = document.createElement('option');
opt.value = '__url';
opt.textContent = 'Custom URL';
sampleSelect.appendChild(opt);
}
sampleSelect.value = '__url';
setHashQuietly(url);

setStatus('compiling', 'Fetching...');
try {
const text = await fetchContent(url);
setEditorContent(text);
} catch (err) {
setStatus('error', 'Fetch failed');
errorText.textContent = err.message;
errorBanner.classList.remove('d-none');
}
setEditorContent(sample.content);
}

/** Parse the current hash and load accordingly */
async function loadFromHash() {
function loadFromHash() {
const hash = getHashValue();
if (!hash) return false;

if (SAMPLES[hash]) {
await loadSample(hash);
return true;
}

// Treat as URL if it starts with http
if (hash.startsWith('http://') || hash.startsWith('https://')) {
await loadFromUrl(hash);
loadSample(hash);
return true;
}

Expand All @@ -315,7 +205,6 @@ async function loadFromHash() {

sampleSelect.addEventListener('change', () => {
const key = sampleSelect.value;
if (key === '__url') return;
loadSample(key);
});
Comment on lines 206 to 209
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index.html still contains <option> entries for removed sample keys (e.g., issue-triage, ci-doctor, etc.). With the fetch path removed and SAMPLES now containing only hello-world, selecting those options calls loadSample(key) which immediately returns and leaves the UI in a confusing/broken state. Consider either (a) updating the sample selector options to match SAMPLES, or (b) adding a guard here that resets the selector to a valid key / shows an error when SAMPLES[key] is missing.

Copilot uses AI. Check for mistakes.

Expand Down
Loading