diff --git a/bricks/api_doc.js b/bricks/api_doc.js new file mode 100644 index 0000000..88ffed6 --- /dev/null +++ b/bricks/api_doc.js @@ -0,0 +1,305 @@ +var bricks = window.bricks || {}; +/* + * ApiDoc widget - renders API documentation from markdown + * Depends on: marked (CDN), highlight.js (CDN, optional) + * Options: + * md_url: string - URL to fetch markdown from + * title: string (optional) - page title + * base_url: string (optional) - API base URL for display + */ + +bricks.ApiDoc = class extends bricks.VBox { + constructor(options) { + if (!options) options = {}; + super(options); + this.md_url = bricks.absurl(this.opts.md_url); + this.api_title = this.opts.title || 'API Documentation'; + this.api_base_url = this.opts.base_url || ''; + this.sections = []; + this.active_section = null; + this._build_layout(); + var self = this; + schedule_once(function() { self._load(); }, 0.1); + } + + _build_layout() { + var d = this.dom_element; + d.classList.add('bricks-apidoc'); + d.innerHTML = ''; + + // Header + var header = document.createElement('div'); + header.className = 'bricks-apidoc-header'; + header.innerHTML = '
' + + this.api_title + '
' + + (this.api_base_url ? '
' + + this.api_base_url + '
' : ''); + d.appendChild(header); + + // Body: sidebar + content + var body = document.createElement('div'); + body.className = 'bricks-apidoc-body'; + d.appendChild(body); + + // Sidebar + var sidebar = document.createElement('div'); + sidebar.className = 'bricks-apidoc-sidebar'; + body.appendChild(sidebar); + this.sidebar_el = sidebar; + + // Content + var content = document.createElement('div'); + content.className = 'bricks-apidoc-content'; + body.appendChild(content); + this.content_el = content; + + // Scroll spy + var self = this; + content.addEventListener('scroll', function() { + self._scroll_spy(); + }); + } + + async _load() { + if (!this.md_url) return; + try { + var md_text = await bricks.tget(this.md_url); + this._render(md_text); + } catch(e) { + this.content_el.innerHTML = + '
Failed to load: ' + + this.md_url + '
'; + bricks.error('ApiDoc load error:', e); + } + } + + _render(md_text) { + // Use marked to parse the full markdown + var html = marked.parse(md_text); + + // Create a temporary container to parse the DOM + var tmp = document.createElement('div'); + tmp.innerHTML = html; + + // Extract sections (h2 elements = API endpoints) + var sections = []; + var current = null; + var children = tmp.children; + + for (var i = 0; i < children.length; i++) { + var el = children[i]; + if (el.tagName === 'H2') { + if (current) sections.push(current); + current = { + heading: el.textContent.trim(), + id: 'api-sec-' + sections.length, + elements: [] + }; + } else if (current) { + current.elements.push(el); + } + } + if (current) sections.push(current); + + this.sections = sections; + this._build_sidebar(); + this._build_content(sections); + + // Highlight code blocks + if (typeof hljs !== 'undefined') { + var blocks = this.content_el.querySelectorAll('pre code'); + blocks.forEach(function(block) { + hljs.highlightElement(block); + }); + } + + // Add copy buttons to code blocks + this._add_copy_buttons(); + + // Activate first section + if (sections.length > 0) { + this._activate_section(sections[0].id); + } + } + + _build_sidebar() { + var sidebar = this.sidebar_el; + sidebar.innerHTML = ''; + + var self = this; + for (var i = 0; i < this.sections.length; i++) { + var sec = this.sections[i]; + var item = document.createElement('div'); + item.className = 'bricks-apidoc-nav-item'; + item.setAttribute('data-section', sec.id); + + // Parse method from heading (e.g. "POST /v1/chat/completions") + var parts = sec.heading.split(' '); + if (parts.length >= 2) { + var method = parts[0].toUpperCase(); + var path = parts.slice(1).join(' '); + var badge = document.createElement('span'); + badge.className = 'bricks-apidoc-method-badge'; + badge.className += ' bricks-apidoc-method-' + method.toLowerCase(); + badge.textContent = method; + item.appendChild(badge); + + var pathEl = document.createElement('span'); + pathEl.className = 'bricks-apidoc-nav-path'; + pathEl.textContent = path.replace('/v1/', '/'); + item.appendChild(pathEl); + } else { + item.textContent = sec.heading; + } + + item.onclick = (function(sid) { + return function() { self._scroll_to(sid); }; + })(sec.id); + + sidebar.appendChild(item); + } + } + + _build_content(sections) { + var content = this.content_el; + content.innerHTML = ''; + + for (var i = 0; i < sections.length; i++) { + var sec = sections[i]; + + // Section container + var section_el = document.createElement('div'); + section_el.className = 'bricks-apidoc-section'; + section_el.id = sec.id; + content.appendChild(section_el); + + // Section heading with method badge + var heading = document.createElement('h2'); + heading.className = 'bricks-apidoc-section-title'; + + var parts = sec.heading.split(' '); + if (parts.length >= 2) { + var method = parts[0].toUpperCase(); + var path = parts.slice(1).join(' '); + heading.innerHTML = '' + method + + '' + path + ''; + } else { + heading.textContent = sec.heading; + } + section_el.appendChild(heading); + + // Section content elements + for (var j = 0; j < sec.elements.length; j++) { + var el = sec.elements[j].cloneNode(true); + this._enhance_element(el); + section_el.appendChild(el); + } + } + } + + _enhance_element(el) { + // Add classes to tables + if (el.tagName === 'TABLE') { + el.className = 'bricks-apidoc-table'; + } + + // Add classes to h3 subsections + if (el.tagName === 'H3') { + el.className = 'bricks-apidoc-subsection'; + } + + // Add classes to pre/code blocks + if (el.tagName === 'PRE') { + var wrapper = document.createElement('div'); + wrapper.className = 'bricks-apidoc-code-wrapper'; + el.parentNode && el.parentNode.insertBefore(wrapper, el); + wrapper.appendChild(el); + } + + // Add classes to inline tables in parent divs + if (el.tagName === 'DIV') { + var tables = el.querySelectorAll('table'); + tables.forEach(function(t) { t.className = 'bricks-apidoc-table'; }); + } + } + + _add_copy_buttons() { + var wrappers = this.content_el.querySelectorAll('.bricks-apidoc-code-wrapper'); + wrappers.forEach(function(wrapper) { + var pre = wrapper.querySelector('pre'); + if (!pre) return; + + var btn = document.createElement('button'); + btn.className = 'bricks-apidoc-copy-btn'; + btn.textContent = 'Copy'; + btn.onclick = function() { + var code = pre.querySelector('code'); + var text = code ? code.textContent : pre.textContent; + navigator.clipboard.writeText(text).then(function() { + btn.textContent = 'Copied!'; + setTimeout(function() { btn.textContent = 'Copy'; }, 2000); + }); + }; + wrapper.insertBefore(btn, pre); + }); + } + + _scroll_to(section_id) { + var el = document.getElementById(section_id); + if (!el) return; + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + this._activate_section(section_id); + } + + _activate_section(section_id) { + if (this.active_section === section_id) return; + this.active_section = section_id; + + // Update sidebar active state + var items = this.sidebar_el.querySelectorAll('.bricks-apidoc-nav-item'); + items.forEach(function(item) { + item.classList.remove('active'); + if (item.getAttribute('data-section') === section_id) { + item.classList.add('active'); + } + }); + } + + _scroll_spy() { + var content = this.content_el; + var sections = content.querySelectorAll('.bricks-apidoc-section'); + var scrollTop = content.scrollTop; + var found = null; + + for (var i = 0; i < sections.length; i++) { + var sec = sections[i]; + if (sec.offsetTop - content.offsetTop <= scrollTop + 60) { + found = sec.id; + } + } + + if (found) { + this._activate_section(found); + } + } + + setValue(v) { + if (typeof v === 'string') { + this._render(v); + } + } + + getValue() { + return this.md_url; + } + + render(params) { + if (params && params.md_url) { + this.md_url = bricks.absurl(params.md_url); + this._load(); + } + } +} + +bricks.Factory.register('ApiDoc', bricks.ApiDoc); diff --git a/bricks/build.sh b/bricks/build.sh index 43b67c0..45c8ebe 100755 --- a/bricks/build.sh +++ b/bricks/build.sh @@ -12,7 +12,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \ line.js pie.js bar.js gobang.js period.js iconbarpage.js \ keypress.js asr.js webspeech.js countdown.js progressbar.js \ qaframe.js svg.js videoplayer.js scatter.js radar.js kline.js \ - heatmap.js map.js qr.js textfiles.js agent.js " + heatmap.js map.js qr.js textfiles.js agent.js api_doc.js " echo ${SOURCES} cat ${SOURCES} > ../dist/bricks.js # uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js diff --git a/bricks/css/api_doc.css b/bricks/css/api_doc.css new file mode 100644 index 0000000..bcc62fa --- /dev/null +++ b/bricks/css/api_doc.css @@ -0,0 +1,285 @@ +/* ApiDoc Widget Styles */ + +.bricks-apidoc { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + overflow: hidden; + background: var(--bg-card, #1a1a2e); + color: var(--text-primary, #e0e0e0); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.bricks-apidoc-header { + padding: 16px 20px; + border-bottom: 1px solid var(--border-color, #2a2a4a); + flex-shrink: 0; +} + +.bricks-apidoc-title { + font-size: 1.4em; + font-weight: 700; + color: var(--text-primary, #fff); +} + +.bricks-apidoc-baseurl { + font-size: 0.85em; + color: var(--text-dim, #888); + margin-top: 4px; + font-family: 'SFMono-Regular', Consolas, monospace; +} + +/* Body: sidebar + content */ +.bricks-apidoc-body { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Sidebar */ +.bricks-apidoc-sidebar { + width: 240px; + min-width: 200px; + border-right: 1px solid var(--border-color, #2a2a4a); + overflow-y: auto; + padding: 8px 0; + flex-shrink: 0; +} + +.bricks-apidoc-nav-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + cursor: pointer; + transition: background 0.15s; + font-size: 0.88em; + border-left: 3px solid transparent; +} + +.bricks-apidoc-nav-item:hover { + background: var(--bg-hover, rgba(255,255,255,0.05)); +} + +.bricks-apidoc-nav-item.active { + background: var(--bg-active, rgba(100,140,255,0.1)); + border-left-color: var(--accent-blue, #648cff); +} + +.bricks-apidoc-nav-path { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--text-secondary, #bbb); +} + +.bricks-apidoc-nav-item.active .bricks-apidoc-nav-path { + color: var(--text-primary, #fff); +} + +/* Method badges */ +.bricks-apidoc-method-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 4px; + font-size: 0.75em; + font-weight: 700; + letter-spacing: 0.5px; + text-transform: uppercase; + flex-shrink: 0; +} + +.bricks-apidoc-method-get { + background: rgba(34,197,94,0.15); + color: #22c55e; +} + +.bricks-apidoc-method-post { + background: rgba(59,130,246,0.15); + color: #3b82f6; +} + +.bricks-apidoc-method-put { + background: rgba(249,115,22,0.15); + color: #f97316; +} + +.bricks-apidoc-method-delete { + background: rgba(239,68,68,0.15); + color: #ef4444; +} + +.bricks-apidoc-method-patch { + background: rgba(168,85,247,0.15); + color: #a855f7; +} + +/* Content area */ +.bricks-apidoc-content { + flex: 1; + overflow-y: auto; + padding: 20px 28px; + scroll-behavior: smooth; +} + +/* Section */ +.bricks-apidoc-section { + margin-bottom: 40px; + padding-bottom: 32px; + border-bottom: 1px solid var(--border-color, #2a2a4a); +} + +.bricks-apidoc-section:last-child { + border-bottom: none; +} + +.bricks-apidoc-section-title { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.08)); +} + +.bricks-apidoc-section-title .bricks-apidoc-method-badge { + font-size: 0.8em; + padding: 4px 10px; +} + +.bricks-apidoc-path { + font-family: 'SFMono-Regular', Consolas, monospace; + font-size: 0.95em; + color: var(--text-primary, #fff); + word-break: break-all; +} + +/* Subsection headings (h3) */ +.bricks-apidoc-subsection, +.bricks-apidoc-section h3 { + font-size: 1em; + font-weight: 600; + color: var(--text-secondary, #ccc); + margin: 20px 0 10px 0; + padding-bottom: 6px; + border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06)); +} + +/* Tables */ +.bricks-apidoc-table, +.bricks-apidoc-section table { + width: 100%; + border-collapse: collapse; + margin: 10px 0; + font-size: 0.88em; +} + +.bricks-apidoc-table th, +.bricks-apidoc-section table th { + background: var(--bg-elevated, rgba(255,255,255,0.04)); + text-align: left; + padding: 8px 12px; + border: 1px solid var(--border-color, #2a2a4a); + color: var(--text-secondary, #aaa); + font-weight: 600; +} + +.bricks-apidoc-table td, +.bricks-apidoc-section table td { + padding: 8px 12px; + border: 1px solid var(--border-color, #2a2a4a); + color: var(--text-primary, #ddd); +} + +.bricks-apidoc-table tr:hover td, +.bricks-apidoc-section table tr:hover td { + background: var(--bg-hover, rgba(255,255,255,0.03)); +} + +/* Inline code */ +.bricks-apidoc-section code { + background: var(--bg-elevated, rgba(255,255,255,0.08)); + padding: 1px 6px; + border-radius: 3px; + font-size: 0.9em; + font-family: 'SFMono-Regular', Consolas, monospace; + color: #e0a8ff; +} + +/* Code blocks */ +.bricks-apidoc-code-wrapper { + position: relative; + margin: 12px 0; +} + +.bricks-apidoc-code-wrapper pre { + background: var(--bg-code, #0d1117) !important; + border: 1px solid var(--border-color, #2a2a4a); + border-radius: 6px; + padding: 14px 16px; + overflow-x: auto; + font-size: 0.85em; + line-height: 1.5; + margin: 0; +} + +.bricks-apidoc-code-wrapper pre code { + background: none; + padding: 0; + color: #c9d1d9; + font-family: 'SFMono-Regular', Consolas, monospace; +} + +/* Copy button */ +.bricks-apidoc-copy-btn { + position: absolute; + top: 8px; + right: 8px; + background: var(--bg-elevated, rgba(255,255,255,0.1)); + border: 1px solid var(--border-color, #2a2a4a); + color: var(--text-secondary, #aaa); + padding: 3px 10px; + border-radius: 4px; + font-size: 0.75em; + cursor: pointer; + transition: all 0.15s; + opacity: 0; +} + +.bricks-apidoc-code-wrapper:hover .bricks-apidoc-copy-btn { + opacity: 1; +} + +.bricks-apidoc-copy-btn:hover { + background: var(--bg-hover, rgba(255,255,255,0.15)); + color: var(--text-primary, #fff); +} + +/* Description paragraphs */ +.bricks-apidoc-section p { + margin: 8px 0; + line-height: 1.6; + color: var(--text-secondary, #ccc); +} + +/* Horizontal rules */ +.bricks-apidoc-section hr { + border: none; + border-top: 1px solid var(--border-color, #2a2a4a); + margin: 16px 0; +} + +/* Error state */ +.bricks-apidoc-error { + padding: 20px; + color: #ef4444; + text-align: center; +} + +/* Responsive: hide sidebar on narrow widths */ +@media (max-width: 700px) { + .bricks-apidoc-sidebar { + display: none; + } +}