Assets: Display extension author in the list (#4551) * Assets: Display extension author in the list * Fix overflow of long text

45a7c6b2aacae8d6c675c2e4999d34757df13af4

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +64 -5Ignore whitespace
public/scripts/extensions/assets/index.js+37 -3
@@ -10,7 +10,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
1010import { executeSlashCommandsWithOptions } from '../../slash-commands.js';
1111import { accountStorage } from '../../util/AccountStorage.js';
1212import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
1313import { t, translate } from '../../i18n.js';
1414export { MODULE_NAME };
1515
1616const MODULE_NAME = 'assets';
@@ -60,6 +60,36 @@ const KNOWN_TYPES = {
6060 'blip': t`Blip sounds`,
6161};
6262
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+
6393async function downloadAssetsList(url) {
6494 updateCurrentAssets().then(async function () {
6595 fetch(url, { cache: 'no-cache' })
@@ -88,7 +118,8 @@ async function downloadAssetsList(url) {
88118 $('#assets_type_select').append($('<option />', { value: '', text: t`All` }));
89119
90120 for (const type of assetTypes) {
91121 const optiontext = $('<option />', { value: type, text: ttranslate([KNOWN_TYPES[type] || type]) });
122+ const option = $('<option />', { value: type, text: text });
92123 $('#assets_type_select').append(option);
93124 }
94125
@@ -184,10 +215,11 @@ async function downloadAssetsList(url) {
184215 const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`;
185216 const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
186217 const toolTag = assetType === 'extension' && asset['tool'];
218+ const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR;
187219
188220 const assetBlock = $('<i></i>')
189221 .append(element)
190222 .append(`<div class="flex-container flexFlowColumn flexNoGap wide100p overflowHidden">
191223 <span class="asset-name flex-container alignitemscenter">
192224 <b>${displayName}</b>
193225 <a class="asset_preview" href="${url}" target="_blank" title="${title}">
@@ -195,6 +227,8 @@ async function downloadAssetsList(url) {
195227 </a>` +
196228 (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' +
197229 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>` : '') +
198232 `</span>
199233 <small class="asset-description">
200234 ${description}
public/scripts/extensions/assets/style.css+27 -2
@@ -35,14 +35,15 @@
3535 color: inherit;
3636}
3737
3838.assets-list-div > i {
3939 display: flex;
4040 flex-direction: row;
4141 align-items: center;
4242 justify-content: left;
4343 padding: 10px 5px;
4444 font-style: normal;
4545 gap: 5px;
46+ border-bottom: 1px solid var(--SmartThemeBorderColor);
4647}
4748
4849.assets-list-div i span:first-of-type {
@@ -173,3 +174,27 @@
173174 opacity: 0.9;
174175 margin-left: 2px;
175176}
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+}