elixir/static/dynamic-references.js
Louis Chauvet 18376d3f7b web: Display documentation before other things
The current order for displaying references is:

- [definitions] prototype, label, typedef, variable, struct, member,
  function
- documentation
- references

This order is not very practical when you are searching for information
about a symbol, as you often want to know if the symbol is documented.

By inverting the order and displaying the documentation first, the user
experience may be improved for finding the documentation. This should not
significantly change the UX for definitions, as there are already many
definitions, and you already have to search for what you want.
2024-11-06 17:17:00 +01:00

247 lines
7.6 KiB
JavaScript

"use strict";
function identUrl(project, ident, version, family) {
return `/api/ident/${project}/${ident}?version=${version}&family=${family}`;
}
/*
"definitions": [
{
"path": "arch/x86_64/kstat.h",
"line": 1,
"type": "struct"
}
],
"references": [
{
"path": "src/stat/fstatat.c",
"line": "78,142",
"type": null
}
],
"documentations": [
{
"path": "Documentation/devicetree/bindings/arm/arm,cci-400.yaml",
"line": "15,17,18",
"type": null
}
]
*/
function generateSymbolDefinitionsHTML(symbolDefinitions, project, version) {
let result = "";
let typesCount = {};
let previous_type = "";
if(symbolDefinitions.length == 0) {
return '<h2>No definitions found in the database</h2>';
}
for(let symbolDefinition of symbolDefinitions) {
if (symbolDefinition.type in typesCount) {
typesCount[symbolDefinition.type] += 1;
} else {
typesCount[symbolDefinition.type] = 1;
}
}
for (let sd of symbolDefinitions) {
if (sd.type != previous_type) {
if (previous_type != '') {
result += '</ul>';
}
result += '<h2>Defined in ' + typesCount[sd.type].toString() + ' files as a ' + sd.type + ':</h2>';
result += '<ul>';
previous_type = sd.type;
}
let ln = sd.line.toString().split(',');
if (ln.length == 1) {
let n = ln[0];
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${n}"><strong>${sd.path}</strong>, line ${n} <em>(as a ${sd.type})</em></a>`;
} else {
if (symbolDefinitions.length > 100) {
let n = ln.length;
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${ln[0]}"><strong>${sd.path}</strong>, <em>${n} times</em> <em>(as a ${sd.type})</em></a>`;
} else {
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${ln[0]}"><strong>${sd.path}</strong> <em>(as a ${sd.type})</em></a>`;
result += '<ul>';
for(let n of ln) {
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${n}">line ${n}</a></li>`;
}
result += '</ul>';
}
}
}
result += '</ul>';
return result;
}
function generateSymbolReferencesHTML(symbolReferences, project, version) {
let result = "";
if(symbolReferences.length == 0) {
return '<h2>No references found in the database</h2>';
}
result += '<h2>Referenced in ' + symbolReferences.length.toString() + ' files:</h2>';
result += '<ul>';
for (let sr of symbolReferences) {
let ln = sr.line.split(',');
if (ln.length == 1) {
let n = ln[0];
result += `<li><a href="/${project}/${version}/source/${sr.path}#L${n}"><strong>${sr.path}</strong>, line ${n}</a>`;
} else {
if(symbolReferences.length > 100) {
let n = ln.length;
result += `<li><a href="/${project}/${version}/source/${sr.path}#L${ln[0]}"><strong>${sr.path}</strong>, <em>${n} times</em></a>`;
} else {
result += `<li><a href="/${project}/${version}/source/${sr.path}#L${ln[0]}"><strong>${sr.path}</strong></a>`;
result += '<ul>'
for(let n of ln) {
result += `<li><a href="/${project}/${version}/source/${sr.path}#L${n}">line ${n}</a>`
}
result += '</ul>'
}
}
}
result += '</ul>'
return result;
}
function generateDocCommentsHTML(symbolDocComments, project, version) {
let result = "";
if (symbolDocComments.length == 0) {
return result;
}
result += '<h2>Documented in ' + symbolDocComments.length.toString() + ' files:</h2>';
result += '<ul>';
for(let sd of symbolDocComments) {
let ln = sd.line.split(',');
if(ln.length == 1) {
let n = ln[0];
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${n}"><strong>${sd.path}</strong>, line ${n}</a>`;
} else {
if(symbolDocComments.length > 100) {
let n = ln.length;
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${ln[0]}"><strong>${sd.path}</strong>, <em>${n} times</em></a>`;
} else {
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${ln[0]}"><strong>${sd.path}</strong></a>`;
result += '<ul>';
for(let n of ln) {
result += `<li><a href="/${project}/${version}/source/${sd.path}#L${n}">line ${n}</a>`;
}
result += '</ul>';
}
}
}
result += '</ul>';
return result;
}
function generateReferencesHTML(data, project, version) {
let symbolDefinitions = data["definitions"];
let symbolReferences = data["references"];
let symbolDocumentations = data["documentations"];
return '<div class="lxrident">' +
generateDocCommentsHTML(symbolDocumentations, project, version) +
generateSymbolDefinitionsHTML(symbolDefinitions, project, version) +
generateSymbolReferencesHTML(symbolReferences, project, version) +
'</div>';
}
function showPopup(referencePopup, target) {
let targetRect = target.getBoundingClientRect();
let x = target.offsetLeft;
let y = target.offsetTop + targetRect.height;
referencePopup.style.visibility = "visible";
referencePopup.style.display = "block";
referencePopup.style.left = `${x}px`;
referencePopup.style.top = `${y}px`;
referencePopup.scrollTop = 0;
referencePopup.scrollLeft = 0;
let referenceRect = referencePopup.getBoundingClientRect();
if((referenceRect.top + referenceRect.height) > window.innerHeight) {
referencePopup.style.top = `${target.offsetTop - referenceRect.height}px`;
}
if((referenceRect.left + referenceRect.width) > window.innerWidth) {
x -= ((referenceRect.left + referenceRect.width) - window.innerWidth);
referencePopup.style.left = `${x}px`;
}
}
function hidePopup(referencePopup) {
referencePopup.style.visibility = "hidden";
referencePopup.style.display = "none";
}
document.addEventListener("DOMContentLoaded", _ => {
let referencePopup = document.getElementById("reference-popup");
let loadingPopup = document.getElementById("loading-popup");
var loadingTimer;
var popupId = 0;
var abortController;
document.body.querySelectorAll(".ident").forEach(el => {
el.addEventListener("click", async ev => {
if (ev.ctrlKey || ev.metaKey || ev.shiftKey) {
return;
}
ev.preventDefault();
let splitPath = ev.target.pathname.split("/");
let [_, project, version, family, _i, ident] = splitPath;
let currentPopupId = ++popupId;
if(abortController !== undefined)
abortController.abort();
abortController = new AbortController();
hidePopup(loadingPopup);
clearTimeout(loadingTimer);
loadingTimer = setTimeout(() => {
showPopup(loadingPopup, ev.target);
}, 200);
function cancelLoadingPopup() {
if (currentPopupId == popupId) {
hidePopup(loadingPopup);
clearTimeout(loadingTimer);
}
}
try {
let result = await fetch(identUrl(project, ident, version, family),
{ signal: abortController.signal })
.then(r => r.json());
if(currentPopupId == popupId) {
referencePopup.innerHTML = generateReferencesHTML(result, project, version);
showPopup(referencePopup, ev.target);
}
} catch(e) {
if(e.name !== "AbortError") {
cancelLoadingPopup();
throw e;
}
}
cancelLoadingPopup();
});
});
referencePopup.addEventListener("click", ev => ev.stopPropagation());
document.body.addEventListener("click", _ => hidePopup(referencePopup));
document.body.addEventListener("keydown", ev => {
if (ev.key === "Escape") {
hidePopup(referencePopup);
}
});
});