github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/deck/static/plugin-help/plugin-help.ts (about) 1 import "code-prettify"; 2 import dialogPolyfill from "dialog-polyfill"; 3 import {Command, Help, PluginHelp} from "../api/help"; 4 import {getParameterByName} from '../common/urls'; 5 import {Language, Prettify} from "./prettify"; 6 7 declare const allHelp: Help; 8 declare const PR: Prettify; 9 10 function redrawOptions(): void { 11 const rs = allHelp.AllRepos.sort(); 12 const sel = document.getElementById("repo") as HTMLSelectElement; 13 while (sel.length > 1) { 14 sel.removeChild(sel.lastChild); 15 } 16 const param = getParameterByName("repo"); 17 rs.forEach((opt) => { 18 const o = document.createElement("option"); 19 o.text = opt; 20 o.selected = !!(param && opt === param); 21 sel.appendChild(o); 22 }); 23 } 24 25 window.onload = (): void => { 26 // set dropdown based on options from query string 27 redrawOptions(); 28 redraw(); 29 // Register dialog 30 const dialog = document.querySelector('dialog') ; 31 dialogPolyfill.registerDialog(dialog); 32 dialog.querySelector('.close')!.addEventListener('click', () => { 33 dialog.close(); 34 }); 35 // Set up dropdown 36 document.querySelector<HTMLSelectElement>('#repo')!.onchange = redraw; 37 }; 38 39 function selectionText(sel: HTMLSelectElement): string { 40 return sel.selectedIndex === 0 ? "" : sel.options[sel.selectedIndex].text; 41 } 42 43 /** 44 * Returns a section to the content of the dialog 45 * 46 * @param title title of the section 47 * @param body body of the section 48 */ 49 function addDialogSection(title: string, body: string | HTMLElement[]): HTMLElement { 50 const container = document.createElement("div"); 51 const sectionTitle = document.createElement("h5"); 52 const sectionBody = document.createElement("div"); 53 54 sectionBody.classList.add("dialog-section-body"); 55 if (Array.isArray(body)) { 56 body.forEach((el) => { 57 sectionBody.appendChild(el); 58 }); 59 } else { 60 sectionBody.innerHTML = body; 61 } 62 63 sectionTitle.classList.add("dialog-section-title"); 64 sectionTitle.innerHTML = title; 65 66 container.classList.add("dialog-section"); 67 container.appendChild(sectionTitle); 68 container.appendChild(sectionBody); 69 70 return container; 71 } 72 73 function genCodeSnippetSummary(snippet: string, language: Language = "yaml"): string { 74 return ` 75 <details> 76 <summary>Example <b>plugins.yaml</b> configuration:</summary> 77 <pre class="prettyprint"><code class="language-${language}">${snippet}</code></pre> 78 </details> 79 `; 80 } 81 82 /** 83 * Return a list of link elements that links to commands. 84 * 85 * @param commands list of commands 86 */ 87 function getLinkableCommands(commands: Command[]): HTMLAnchorElement[] { 88 const result: HTMLAnchorElement[] = []; 89 commands.forEach((command) => { 90 const commandName = extractCommandName(command.Examples[0]); 91 const link = document.createElement("a"); 92 link.href = `/command-help#${ commandName}`; 93 link.innerHTML = command.Examples[0]; 94 link.classList.add("plugin-help-command-link"); 95 result.push(link); 96 }); 97 return result; 98 } 99 100 /** 101 * Create a card for a plugin. 102 * 103 * @param repo repo name 104 * @param name name of the plugin 105 * @param pluginObj plugin object 106 * @return the card element that contains the plugin 107 */ 108 function createPlugin(repo: string, name: string, pluginObj: {isExternal: boolean, plugin: PluginHelp}): HTMLElement { 109 const isExternal = pluginObj.isExternal; 110 const plugin = pluginObj.plugin; 111 112 const title = document.createElement("h3"); 113 title.innerHTML = name; 114 title.classList.add("mdl-card__title-text"); 115 const supportTitle = document.createElement("div"); 116 supportTitle.innerHTML = isExternal ? " external plugin" : ""; 117 supportTitle.classList.add("mdl-card__subtitle-text"); 118 const cardTitle = document.createElement("div"); 119 cardTitle.classList.add("mdl-card__title"); 120 cardTitle.appendChild(title); 121 cardTitle.appendChild(supportTitle); 122 123 const cardDesc = document.createElement("div"); 124 cardDesc.innerHTML = getFirstSentence(plugin.Description); 125 cardDesc.classList.add("mdl-card__supporting-text"); 126 127 const cardAction = document.createElement("div"); 128 const actionButton = document.createElement("a"); 129 actionButton.innerHTML = "Details"; 130 actionButton.classList.add(...["mdl-button", "mdl-button--colored", "mdl-js-button", "mdl-js-ripple-effect"]); 131 actionButton.addEventListener("click", () => { 132 const dialogElement = document.querySelector("dialog") ; 133 const titleElement = dialogElement.querySelector(".mdl-dialog__title")!; 134 const contentElement = dialogElement.querySelector(".mdl-dialog__content")!; 135 136 while (contentElement.firstChild) { 137 contentElement.removeChild(contentElement.firstChild); 138 } 139 140 titleElement.innerHTML = name; 141 if (plugin.Description) { 142 contentElement.appendChild(addDialogSection("Description", plugin.Description)); 143 } 144 if (plugin.Events) { 145 const sectionContent = `[${plugin.Events.sort().join(", ")}]`; 146 contentElement.appendChild(addDialogSection("Events handled", sectionContent)); 147 } 148 if (plugin.Config) { 149 const sectionContent = plugin.Config ? plugin.Config[repo] : ""; 150 const sectionTitle = 151 repo === "" ? "Configuration(global)" : `Configuration(${repo})`; 152 if (sectionContent && sectionContent !== "") { 153 contentElement.appendChild(addDialogSection(sectionTitle, sectionContent)); 154 } 155 } 156 if (plugin.Commands) { 157 const sectionContent = getLinkableCommands(plugin.Commands); 158 contentElement.appendChild(addDialogSection("Commands", sectionContent)); 159 } 160 if (plugin.Snippet) { 161 contentElement.appendChild(addDialogSection("Snippet", genCodeSnippetSummary(plugin.Snippet))); 162 PR.prettyPrint(); 163 } 164 dialogElement.showModal(); 165 }); 166 cardAction.appendChild(actionButton); 167 cardAction.classList.add("mdl-card__actions", "mdl-card--border"); 168 169 const card = document.createElement("div"); 170 card.appendChild(cardTitle); 171 card.appendChild(cardDesc); 172 card.appendChild(cardAction); 173 174 card.classList.add("plugin-help-card", "mdl-card", "mdl-shadow--2dp"); 175 if (isDeprecated(plugin.Description)) { 176 card.classList.add("deprecated"); 177 } 178 return card; 179 } 180 181 /** 182 * Takes an org/repo string and a repo to plugin map and returns the plugins 183 * that apply to the repo. 184 * 185 * @param repoSel repo name 186 * @param repoPlugins maps plugin name to plugin 187 */ 188 function applicablePlugins(repoSel: string, repoPlugins: {[key: string]: string[]}): string[] { 189 if (repoSel === "") { 190 const all = repoPlugins[""]; 191 if (all) { 192 return all.sort(); 193 } 194 return []; 195 } 196 const parts = repoSel.split("/"); 197 const byOrg = repoPlugins[parts[0]]; 198 let plugins: string[] = []; 199 if (byOrg && byOrg !== []) { 200 plugins = plugins.concat(byOrg); 201 } 202 const pluginNames = repoPlugins[repoSel]; 203 if (pluginNames) { 204 pluginNames.forEach((pluginName) => { 205 if (!plugins.includes(pluginName)) { 206 plugins.push(pluginName); 207 } 208 }); 209 } 210 return plugins.sort(); 211 } 212 213 /** 214 * Redraw plugin cards. 215 * 216 * @param {string} repo repo name. 217 * @param {Map<string, Object>} helpMap maps a plugin name to a plugin. 218 */ 219 function redrawPlugin(repo: string, helpMap: Map<string, {isExternal: boolean, plugin: PluginHelp}>): void { 220 const container = document.querySelector("#plugin-container")!; 221 while (container.childElementCount !== 0) { 222 container.removeChild(container.firstChild); 223 } 224 const names = helpMap.keys(); 225 const nameArray = Array.from(names).sort(); 226 nameArray.forEach((name) => { 227 container.appendChild(createPlugin(repo, name, helpMap.get(name)!)); 228 }); 229 } 230 231 /** 232 * Redraws the content of the page. 233 */ 234 function redraw(): void { 235 const repoSel = selectionText(document.getElementById("repo") as HTMLSelectElement); 236 if (window.history && window.history.replaceState !== undefined) { 237 if (repoSel !== "") { 238 history.replaceState(null, "", `/plugins?repo=${ 239 encodeURIComponent(repoSel)}`); 240 } else { 241 history.replaceState(null, "", "/plugins"); 242 } 243 } 244 redrawOptions(); 245 246 const plugins: Map<string, {isExternal: boolean, plugin: PluginHelp}> = new Map(); 247 applicablePlugins(repoSel, allHelp.RepoPlugins) 248 .forEach((name) => { 249 if (allHelp.PluginHelp[name]) { 250 plugins.set( 251 name, 252 { 253 isExternal: false, 254 plugin: allHelp.PluginHelp[name], 255 }); 256 } 257 }); 258 applicablePlugins(repoSel, allHelp.RepoExternalPlugins) 259 .forEach((name) => { 260 if (allHelp.ExternalPluginHelp[name]) { 261 plugins.set( 262 name, 263 { 264 isExternal: true, 265 plugin: allHelp.ExternalPluginHelp[name], 266 }); 267 } 268 }); 269 redrawPlugin(repoSel, plugins); 270 } 271 272 /** 273 * Returns first sentence from plugin's example. 274 */ 275 function getFirstSentence(text: string): string { 276 const fullStop = text.indexOf("."); 277 return fullStop === -1 ? text : text.slice(0, fullStop + 1); 278 } 279 280 /** 281 * Returns true if the plugin is deprecated. 282 */ 283 function isDeprecated(text: string): boolean { 284 const dictionary = ["deprecated!"]; 285 text = text.toLowerCase(); 286 for (const entry of dictionary) { 287 if (text.indexOf(entry) !== -1) { 288 return true; 289 } 290 } 291 return false; 292 } 293 294 /** 295 * Extracts a command name from a command example. It takes the first example, 296 * with out the slash, as the name for the command. Also, any '-' character is 297 * replaced by '_' to make the name valid in the address. 298 */ 299 function extractCommandName(commandExample: string): string { 300 const command = commandExample.split(" "); 301 if (!command || command.length === 0) { 302 throw new Error("Cannot extract command name."); 303 } 304 return command[0].slice(1).split("-").join("_"); 305 }