306 lines
8.0 KiB
JavaScript
306 lines
8.0 KiB
JavaScript
var bricks = window.bricks || {};
|
|
/*
|
|
* ApiDoc widget - renders API documentation from markdown
|
|
* Depends on: marked (/bricks/3parties/marked.js, loaded by header.tmpl)
|
|
* 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 = '<div class="bricks-apidoc-title">' +
|
|
this.api_title + '</div>' +
|
|
(this.api_base_url ? '<div class="bricks-apidoc-baseurl">' +
|
|
this.api_base_url + '</div>' : '');
|
|
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 =
|
|
'<div class="bricks-apidoc-error">Failed to load: ' +
|
|
this.md_url + '</div>';
|
|
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 = '<span class="bricks-apidoc-method-badge bricks-apidoc-method-' +
|
|
method.toLowerCase() + '">' + method +
|
|
'</span><span class="bricks-apidoc-path">' + path + '</span>';
|
|
} 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);
|