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 | import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; | 10 | import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; |
| 11 | import { accountStorage } from '../../util/AccountStorage.js'; | 11 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; | 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 13 | import { t } from '../../i18n.js'; | 13 | import { t, translate } from '../../i18n.js'; |
| 14 | export { MODULE_NAME }; | 14 | export { MODULE_NAME }; |
| 15 | 15 | ||
| 16 | const MODULE_NAME = 'assets'; | 16 | const MODULE_NAME = 'assets'; |
| @@ -60,6 +60,36 @@ const KNOWN_TYPES = { | |||
| 60 | 'blip': t`Blip sounds`, | 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 | async function downloadAssetsList(url) { | 93 | async function downloadAssetsList(url) { |
| 64 | updateCurrentAssets().then(async function () { | 94 | updateCurrentAssets().then(async function () { |
| 65 | fetch(url, { cache: 'no-cache' }) | 95 | fetch(url, { cache: 'no-cache' }) |
| @@ -88,7 +118,8 @@ async function downloadAssetsList(url) { | |||
| 88 | $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); | 118 | $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); |
| 89 | 119 | ||
| 90 | for (const type of assetTypes) { | 120 | for (const type of assetTypes) { |
| 91 | const option = $('<option />', { value: type, text: t([KNOWN_TYPES[type] || type]) }); | 121 | const text = translate(KNOWN_TYPES[type] || type); |
| 122 | const option = $('<option />', { value: type, text: text }); | ||
| 92 | $('#assets_type_select').append(option); | 123 | $('#assets_type_select').append(option); |
| 93 | } | 124 | } |
| 94 | 125 | ||
| @@ -184,10 +215,11 @@ async function downloadAssetsList(url) { | |||
| 184 | const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`; | 215 | const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`; |
| 185 | const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; | 216 | const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; |
| 186 | const toolTag = assetType === 'extension' && asset['tool']; | 217 | const toolTag = assetType === 'extension' && asset['tool']; |
| 218 | const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR; | ||
| 187 | 219 | ||
| 188 | const assetBlock = $('<i></i>') | 220 | const assetBlock = $('<i></i>') |
| 189 | .append(element) | 221 | .append(element) |
| 190 | .append(`<div class="flex-container flexFlowColumn flexNoGap"> | 222 | .append(`<div class="flex-container flexFlowColumn flexNoGap wide100p overflowHidden"> |
| 191 | <span class="asset-name flex-container alignitemscenter"> | 223 | <span class="asset-name flex-container alignitemscenter"> |
| 192 | <b>${displayName}</b> | 224 | <b>${displayName}</b> |
| 193 | <a class="asset_preview" href="${url}" target="_blank" title="${title}"> | 225 | <a class="asset_preview" href="${url}" target="_blank" title="${title}"> |
| @@ -195,6 +227,8 @@ async function downloadAssetsList(url) { | |||
| 195 | </a>` + | 227 | </a>` + |
| 196 | (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' + | 228 | (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' + |
| 197 | t`Tool` + '</span>' : '') + | 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 | `</span> | 232 | `</span> |
| 199 | <small class="asset-description"> | 233 | <small class="asset-description"> |
| 200 | ${description} | 234 | ${description} |
| @@ -40,9 +40,10 @@ | |||
| 40 | flex-direction: row; | 40 | flex-direction: row; |
| 41 | align-items: center; | 41 | align-items: center; |
| 42 | justify-content: left; | 42 | justify-content: left; |
| 43 | padding: 5px; | 43 | padding: 10px 5px; |
| 44 | font-style: normal; | 44 | font-style: normal; |
| 45 | gap: 5px; | 45 | gap: 5px; |
| 46 | border-bottom: 1px solid var(--SmartThemeBorderColor); | ||
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | .assets-list-div i span:first-of-type { | 49 | .assets-list-div i span:first-of-type { |
| @@ -173,3 +174,27 @@ | |||
| 173 | opacity: 0.9; | 174 | opacity: 0.9; |
| 174 | margin-left: 2px; | 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 | } | ||