Assets: Display extension author in the list (#4551) * Assets: Display extension author in the list * Fix overflow of long text
Signed| @@ -10,7 +10,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; | ||
| 10 | 10 | import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; |
| 11 | 11 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 12 | 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 13 | 13 | import { t, translate } from '../../i18n.js'; |
| 14 | 14 | export { MODULE_NAME }; |
| 15 | 15 | |
| 16 | 16 | const MODULE_NAME = 'assets'; |
| @@ -60,6 +60,36 @@ const KNOWN_TYPES = { | ||
| 60 | 60 | 'blip': t`Blip sounds`, |
| 61 | 61 | }; |
| 62 | 62 | |
| 63 | +const EMPTY_AUTHOR = { | |
| 64 | + name: '', | |
| 65 | + url: '', | |
| 66 | +}; | |
| 67 | + | |
| 68 | +/** | |
| 69 | + * Extracts the repository author from a given URL. | |
| 70 | + * @param {string} url - The URL of the repository. | |
| 71 | + * @returns {{name: string, url: string}} Object containing the author's name and URL, or empty strings if not found. | |
| 72 | + */ | |
| 73 | +function getAuthorFromUrl(url) { | |
| 74 | + const result = structuredClone(EMPTY_AUTHOR); | |
| 75 | + | |
| 76 | + try { | |
| 77 | + const parsedUrl = new URL(url); | |
| 78 | + const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0); | |
| 79 | + | |
| 80 | + // TODO: Handle non-GitHub URLs if needed | |
| 81 | + if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) { | |
| 82 | + result.name = pathSegments[0]; | |
| 83 | + result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`; | |
| 84 | + } | |
| 85 | + } | |
| 86 | + catch (error) { | |
| 87 | + console.debug(DEBUG_PREFIX, 'Error parsing URL:', error); | |
| 88 | + } | |
| 89 | + | |
| 90 | + return result; | |
| 91 | +} | |
| 92 | + | |
| 63 | 93 | async function downloadAssetsList(url) { |
| 64 | 94 | updateCurrentAssets().then(async function () { |
| 65 | 95 | fetch(url, { cache: 'no-cache' }) |
| @@ -88,7 +118,8 @@ async function downloadAssetsList(url) { | ||
| 88 | 118 | $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); |
| 89 | 119 | |
| 90 | 120 | for (const type of assetTypes) { |
| 91 | 121 | const optiontext = $('<option />', { value: type, text: ttranslate([KNOWN_TYPES[type] || type]) }); |
| 122 | + const option = $('<option />', { value: type, text: text }); | |
| 92 | 123 | $('#assets_type_select').append(option); |
| 93 | 124 | } |
| 94 | 125 | |
| @@ -184,10 +215,11 @@ async function downloadAssetsList(url) { | ||
| 184 | 215 | const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`; |
| 185 | 216 | const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; |
| 186 | 217 | const toolTag = assetType === 'extension' && asset['tool']; |
| 218 | + const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR; | |
| 187 | 219 | |
| 188 | 220 | const assetBlock = $('<i></i>') |
| 189 | 221 | .append(element) |
| 190 | 222 | .append(`<div class="flex-container flexFlowColumn flexNoGap wide100p overflowHidden"> |
| 191 | 223 | <span class="asset-name flex-container alignitemscenter"> |
| 192 | 224 | <b>${displayName}</b> |
| 193 | 225 | <a class="asset_preview" href="${url}" target="_blank" title="${title}"> |
| @@ -195,6 +227,8 @@ async function downloadAssetsList(url) { | ||
| 195 | 227 | </a>` + |
| 196 | 228 | (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' + |
| 197 | 229 | t`Tool` + '</span>' : '') + |
| 230 | + '<span class="expander"></span>' + | |
| 231 | + (author.name ? `<a href="${author.url}" target="_blank" class="asset-author-info"><i class="fa-solid fa-at fa-xs"></i><span>${author.name}</span></a>` : '') + | |
| 198 | 232 | `</span> |
| 199 | 233 | <small class="asset-description"> |
| 200 | 234 | ${description} |
| @@ -35,14 +35,15 @@ | ||
| 35 | 35 | color: inherit; |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | 38 | .assets-list-div > i { |
| 39 | 39 | display: flex; |
| 40 | 40 | flex-direction: row; |
| 41 | 41 | align-items: center; |
| 42 | 42 | justify-content: left; |
| 43 | 43 | padding: 10px 5px; |
| 44 | 44 | font-style: normal; |
| 45 | 45 | gap: 5px; |
| 46 | + border-bottom: 1px solid var(--SmartThemeBorderColor); | |
| 46 | 47 | } |
| 47 | 48 | |
| 48 | 49 | .assets-list-div i span:first-of-type { |
| @@ -173,3 +174,27 @@ | ||
| 173 | 174 | opacity: 0.9; |
| 174 | 175 | margin-left: 2px; |
| 175 | 176 | } |
| 177 | + | |
| 178 | +.asset-name .asset-author-info { | |
| 179 | + display: flex; | |
| 180 | + align-items: baseline; | |
| 181 | + gap: 2px; | |
| 182 | + opacity: 0.7; | |
| 183 | + font-size: 0.85em; | |
| 184 | + overflow: hidden; | |
| 185 | +} | |
| 186 | + | |
| 187 | +.asset-name .asset-author-info>span { | |
| 188 | + white-space: nowrap; | |
| 189 | + overflow: hidden; | |
| 190 | + text-overflow: ellipsis; | |
| 191 | +} | |
| 192 | + | |
| 193 | +.asset-name .asset-author-info:hover { | |
| 194 | + opacity: 1; | |
| 195 | + transition: opacity var(--animation-duration) ease-in-out; | |
| 196 | +} | |
| 197 | + | |
| 198 | +.asset-name>b { | |
| 199 | + font-weight: 600; | |
| 200 | +} | |