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