( cookbook: Cookbook, recipe: Recipe, selectedLanguage: string | null, highlightedName?: string )
| 131 | } |
| 132 | |
| 133 | function renderRecipeCard( |
| 134 | cookbook: Cookbook, |
| 135 | recipe: Recipe, |
| 136 | selectedLanguage: string | null, |
| 137 | highlightedName?: string |
| 138 | ): string { |
| 139 | const recipeKey = `${cookbook.id}-${recipe.id}`; |
| 140 | const tags = recipe.tags |
| 141 | .map((tag) => `<span class="recipe-tag">${escapeHtml(tag)}</span>`) |
| 142 | .join(""); |
| 143 | const titleHtml = highlightedName || escapeHtml(recipe.name); |
| 144 | |
| 145 | if (recipe.external && recipe.url) { |
| 146 | const authorHtml = recipe.author |
| 147 | ? `<span class="recipe-author">by ${ |
| 148 | recipe.author.url |
| 149 | ? `<a href="${sanitizeUrl( |
| 150 | recipe.author.url |
| 151 | )}" target="_blank" rel="noopener">${escapeHtml( |
| 152 | recipe.author.name |
| 153 | )}</a>` |
| 154 | : escapeHtml(recipe.author.name) |
| 155 | }</span>` |
| 156 | : ""; |
| 157 | |
| 158 | return ` |
| 159 | <div class="recipe-card external" data-recipe="${escapeHtml(recipeKey)}"> |
| 160 | <div class="recipe-header"> |
| 161 | <h3>${titleHtml}</h3> |
| 162 | <span class="recipe-badge external-badge" title="External project"> |
| 163 | <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" aria-hidden="true"> |
| 164 | <path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"/> |
| 165 | </svg> |
| 166 | Community |
| 167 | </span> |
| 168 | </div> |
| 169 | <p class="recipe-description">${escapeHtml(recipe.description)}</p> |
| 170 | ${authorHtml ? `<div class="recipe-author-line">${authorHtml}</div>` : ""} |
| 171 | <div class="recipe-tags">${tags}</div> |
| 172 | <div class="recipe-actions"> |
| 173 | <a href="${sanitizeUrl( |
| 174 | recipe.url |
| 175 | )}" class="btn btn-primary btn-small" target="_blank" rel="noopener"> |
| 176 | <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" aria-hidden="true"> |
| 177 | <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/> |
| 178 | </svg> |
| 179 | View on GitHub |
| 180 | </a> |
| 181 | </div> |
| 182 | </div> |
| 183 | `; |
| 184 | } |
| 185 | |
| 186 | const displayLanguage = selectedLanguage || cookbook.languages?.[0]?.id || "nodejs"; |
| 187 | const variant = recipe.variants[displayLanguage]; |
| 188 | const langIndicators = (cookbook.languages ?? []) |
| 189 | .filter((language) => recipe.variants[language.id]) |
| 190 | .map( |
no test coverage detected