diff --git a/tsconfig.json b/tsconfig.json index 92cf43fbf82..bcecb172172 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,8 +35,6 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "strictPropertyInitialization": false, - "useUnknownInCatchVariables": false, "stripInternal": true, "verbatimModuleSyntax": true, "types": [ diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue index 97ce07cc350..146a65995a6 100644 --- a/web_src/js/components/RepoCodeFrequency.vue +++ b/web_src/js/components/RepoCodeFrequency.vue @@ -21,6 +21,7 @@ import { type DayDataObject, } from '../utils/time.ts'; import {chartJsColors} from '../utils/color.ts'; +import {errorMessage} from '../modules/errors.ts'; import {sleep} from '../utils.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; import {onMounted, shallowRef} from 'vue'; @@ -78,7 +79,7 @@ async function fetchGraphData() { errorText.value = response.statusText; } } catch (err) { - errorText.value = err.message; + errorText.value = errorMessage(err); } finally { isLoading.value = false; } diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index db2f84ecde4..3fd34081e04 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -24,6 +24,7 @@ import { fillEmptyStartDaysWithZeroes, } from '../utils/time.ts'; import {chartJsColors} from '../utils/color.ts'; +import {errorMessage} from '../modules/errors.ts'; import {sleep} from '../utils.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; import {fomanticQuery} from '../modules/fomantic/base.ts'; @@ -166,7 +167,7 @@ export default defineComponent({ this.errorText = response.statusText; } } catch (err) { - this.errorText = err.message; + this.errorText = errorMessage(err); } finally { this.isLoading = false; } diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue index 7eb5c7f289b..b58e84e55d6 100644 --- a/web_src/js/components/RepoRecentCommits.vue +++ b/web_src/js/components/RepoRecentCommits.vue @@ -20,6 +20,7 @@ import { type DayDataObject, } from '../utils/time.ts'; import {chartJsColors} from '../utils/color.ts'; +import {errorMessage} from '../modules/errors.ts'; import {sleep} from '../utils.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; import {onMounted, ref, shallowRef} from 'vue'; @@ -74,7 +75,7 @@ async function fetchGraphData() { errorText.value = response.statusText; } } catch (err) { - errorText.value = err.message; + errorText.value = errorMessage(err); } finally { isLoading.value = false; } diff --git a/web_src/js/features/admin/config.ts b/web_src/js/features/admin/config.ts index 5df81412f9b..96c7dcf84bb 100644 --- a/web_src/js/features/admin/config.ts +++ b/web_src/js/features/admin/config.ts @@ -2,6 +2,7 @@ import {showTemporaryTooltip} from '../../modules/tippy.ts'; import {POST} from '../../modules/fetch.ts'; import {registerGlobalInitFunc} from '../../modules/observer.ts'; import {queryElems} from '../../utils/dom.ts'; +import {errorMessage} from '../../modules/errors.ts'; import {submitFormFetchAction} from '../common-fetch-action.ts'; const {appSubUrl} = window.config; @@ -26,7 +27,7 @@ function initSystemConfigAutoCheckbox(el: HTMLInputElement) { const json: Record = await resp.json(); if (json.errorMessage) throw new Error(json.errorMessage); } catch (ex) { - showTemporaryTooltip(el, ex.toString()); + showTemporaryTooltip(el, errorMessage(ex)); el.checked = !el.checked; } }); diff --git a/web_src/js/features/citation.ts b/web_src/js/features/citation.ts index 1abd960366e..80eaabcc585 100644 --- a/web_src/js/features/citation.ts +++ b/web_src/js/features/citation.ts @@ -1,4 +1,5 @@ import {getCurrentLocale} from '../utils.ts'; +import {errorMessage} from '../modules/errors.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {localUserSettings} from '../modules/user-settings.ts'; @@ -46,7 +47,7 @@ export async function initCitationFileCopyContent() { try { await initInputCitationValue(citationCopyApa, citationCopyBibtex); } catch (e) { - console.error(`initCitationFileCopyContent error: ${e}`, e); + console.error(`initCitationFileCopyContent error: ${errorMessage(e)}`, e); return; } updateUi(); diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts index 24f4ad2e02f..595bb96c854 100644 --- a/web_src/js/features/common-fetch-action.ts +++ b/web_src/js/features/common-fetch-action.ts @@ -1,6 +1,7 @@ import {GET, request} from '../modules/fetch.ts'; import {hideToastsAll, showErrorToast} from '../modules/toast.ts'; import {addDelegatedEventListener, createElementFromHTML} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts'; import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; import {registerGlobalSelectorFunc} from '../modules/observer.ts'; @@ -134,10 +135,10 @@ async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) { return; } await handleFetchActionError(resp); - } catch (e) { - if (e.name !== 'AbortError') { - console.error(`Fetch action request error:`, e); - showErrorToast(`Error: ${e.message ?? e}`); + } catch (err) { + if ((err as Error).name !== 'AbortError') { + console.error(`Fetch action request error:`, err); + showErrorToast(`Error: ${errorMessage(err)}`); } } finally { toggleLoadingIndicator(el, opt, false); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index f16a71a6c57..da7fcd85861 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -71,26 +71,26 @@ export class ComboMarkdownEditor { options: ComboMarkdownEditorOptions; - tabEditor: HTMLElement; - tabPreviewer: HTMLElement; + tabEditor?: HTMLElement; + tabPreviewer?: HTMLElement; - supportEasyMDE: boolean; + supportEasyMDE!: boolean; easyMDE: any; easyMDEToolbarActions: any; easyMDEToolbarDefault: any; - textarea: ComboMarkdownEditorTextarea; - textareaMarkdownToolbar: HTMLElement; + textarea!: ComboMarkdownEditorTextarea; + textareaMarkdownToolbar!: HTMLElement; textareaAutosize: any; - buttonMonospace: HTMLButtonElement; + buttonMonospace!: HTMLButtonElement; - dropzone: HTMLElement | null; + dropzone: HTMLElement | null = null; attachedDropzoneInst: any; - previewMode: string; - previewUrl: string; - previewContext: string; + previewMode!: string; + previewUrl!: string; + previewContext!: string; constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) { if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized'); @@ -291,7 +291,7 @@ export class ComboMarkdownEditor { } switchTabToEditor() { - this.tabEditor.click(); + this.tabEditor!.click(); // when this function is called, the tab must exist } prepareEasyMDEToolbarActions() { diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index 6ff0cc5a3ae..84076dc93b8 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -5,6 +5,7 @@ import {showTemporaryTooltip} from '../modules/tippy.ts'; import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {isImageFile, isVideoFile} from '../utils.ts'; import type Dropzone from '@deltablot/dropzone'; @@ -149,7 +150,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) { } catch (error) { // TODO: if listing the existing attachments failed, it should stop from operating the content or attachments, // otherwise the attachments might be lost. - showErrorToast(`Failed to load attachments: ${error}`); + showErrorToast(`Failed to load attachments: ${errorMessage(error)}`); console.error(error); } }); diff --git a/web_src/js/features/file-view.ts b/web_src/js/features/file-view.ts index b9a5dd5094f..eed4ba0cd31 100644 --- a/web_src/js/features/file-view.ts +++ b/web_src/js/features/file-view.ts @@ -2,6 +2,7 @@ import type {InplaceRenderPlugin} from '../render/plugin.ts'; import {newInplacePluginPdfViewer} from '../render/plugins/inplace-pdf-viewer.ts'; import {registerGlobalInitFunc} from '../modules/observer.ts'; import {createElementFromHTML} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {html} from '../utils/html.ts'; import {basename} from '../utils.ts'; @@ -30,7 +31,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str rendered = true; } } catch (e) { - errorMsg = `${e}`; + errorMsg = errorMessage(e); } finally { container.classList.remove('is-loading'); } diff --git a/web_src/js/features/imagediff.ts b/web_src/js/features/imagediff.ts index 1e89aa8b6ec..5351d35ffe0 100644 --- a/web_src/js/features/imagediff.ts +++ b/web_src/js/features/imagediff.ts @@ -94,8 +94,8 @@ function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageEleme } class ImageDiff { - containerEl: HTMLElement; - diffContainerWidth: number; + containerEl!: HTMLElement; + diffContainerWidth!: number; async init(containerEl: HTMLElement) { this.containerEl = containerEl; diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index e8fb257c185..65b89cf2612 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -1,4 +1,5 @@ import {queryElems} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {sleep} from '../utils.ts'; @@ -26,7 +27,7 @@ async function onDownloadArchive(e: Event) { window.location.href = el.href; // the archive is ready, start real downloading } catch (e) { console.error(e); - showErrorToast(`Failed to download the archive: ${e}`, {duration: 2500}); + showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500}); } finally { targetLoading.classList.remove('is-loading', 'loading-icon-2px'); } diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index 500c86fec6a..f866df3526d 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -6,6 +6,7 @@ import {initViewedCheckboxListenerFor, initExpandAndCollapseFilesButton} from '. import {initImageDiff} from './imagediff.ts'; import {showErrorToast} from '../modules/toast.ts'; import {queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {POST, GET} from '../modules/fetch.ts'; import {createTippy} from '../modules/tippy.ts'; import {invertFileFolding} from './file-fold.ts'; @@ -85,7 +86,7 @@ function initRepoDiffConversationForm() { } } catch (error) { console.error('Error:', error); - showErrorToast(`Submit form failed: ${error}`); + showErrorToast(`Submit form failed: ${errorMessage(error)}`); } finally { form?.classList.remove('is-loading'); } diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index 4a112368f46..198951d41f4 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -3,6 +3,7 @@ import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} fr import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; import {convertHtmlToMarkdown} from '../markup/html2markdown.ts'; import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts'; @@ -73,7 +74,7 @@ async function tryOnEditContent(e: Event) { } comboMarkdownEditor.dropzoneSubmitReload(); } catch (error) { - showErrorToast(`Failed to save the content: ${error}`); + showErrorToast(`Failed to save the content: ${errorMessage(error)}`); console.error(error); } finally { renderContent.classList.remove('is-loading'); diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index c0cd3c7f22c..046c1beee56 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -2,6 +2,7 @@ import {updateIssuesMeta} from './repo-common.ts'; import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts'; import {html, htmlRaw} from '../utils/html.ts'; import {confirmModal} from './comp/ConfirmModal.ts'; +import {errorMessage} from '../modules/errors.ts'; import {showErrorToast} from '../modules/toast.ts'; import {createSortable} from '../modules/sortable.ts'; import {DELETE, POST} from '../modules/fetch.ts'; @@ -87,7 +88,9 @@ function initRepoIssueListCheckboxes() { await updateIssuesMeta(url, action, issueIDs, elementId); window.location.reload(); } catch (err) { - showErrorToast(err.responseJSON?.error ?? err.message); + // FIXME: this logic (including updateIssuesMeta) is not right, should refactor to our JSONError framework + const e = err as {responseJSON?: {error: string}}; + showErrorToast(e.responseJSON?.error ?? errorMessage(err)); } }, )); diff --git a/web_src/js/features/repo-issue-sidebar-combolist.ts b/web_src/js/features/repo-issue-sidebar-combolist.ts index ab29a18f923..0cea54bbbdb 100644 --- a/web_src/js/features/repo-issue-sidebar-combolist.ts +++ b/web_src/js/features/repo-issue-sidebar-combolist.ts @@ -2,6 +2,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts'; import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {parseDom} from '../utils.ts'; export function syncIssueMainContentTimelineItems(oldMainContent: Element, newMainContent: Element) { @@ -42,7 +43,7 @@ export class IssueSidebarComboList { elDropdown: HTMLElement; elList: HTMLElement | null; elComboValue: HTMLInputElement; - initialValues: string[]; + initialValues: string[] = []; container: HTMLElement; elIssueMainContent: HTMLElement; @@ -129,7 +130,7 @@ export class IssueSidebarComboList { await this.reloadPagePartially(); } catch (e) { console.error('Failed to update to backend', e); - showErrorToast(`Failed to update to backend: ${e}`); + showErrorToast(`Failed to update to backend: ${errorMessage(e)}`); } finally { this.elIssueSidebar.classList.remove('is-loading'); } diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 03b0c93d85f..7322432fe87 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -1,3 +1,4 @@ +import {errorMessage} from '../modules/errors.ts'; import {htmlEscape} from '../utils/html.ts'; import {createTippy} from '../modules/tippy.ts'; import { @@ -425,7 +426,7 @@ export function initRepoIssueTitleEdit() { window.location.reload(); } catch (error) { console.error(error); - showErrorToast(error.message); + showErrorToast(errorMessage(error)); } }); } diff --git a/web_src/js/features/repo-settings-branches.ts b/web_src/js/features/repo-settings-branches.ts index 858140adc2f..4e7e74cbc77 100644 --- a/web_src/js/features/repo-settings-branches.ts +++ b/web_src/js/features/repo-settings-branches.ts @@ -2,6 +2,7 @@ import {createSortable} from '../modules/sortable.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {queryElemChildren} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; export function initRepoSettingsBranchesDrag() { const protectedBranchesList = document.querySelector('#protected-branches-list'); @@ -23,8 +24,7 @@ export function initRepoSettingsBranchesDrag() { }, }); } catch (err) { - const errorMessage = String(err); - showErrorToast(`Failed to update branch protection rule priority:, error: ${errorMessage}`); + showErrorToast(`Failed to update branch protection rule priority: ${errorMessage(err)}`); } })(); }, diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index 774d41dce0f..f30f4a7f5f4 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -1,5 +1,6 @@ import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.ts'; import {hideElem, showElem} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {GET, POST} from '../modules/fetch.ts'; const {appSubUrl} = window.config; @@ -80,7 +81,7 @@ async function loginPasskey() { window.location.href = reply?.redirect ?? `${appSubUrl}/`; } catch (err) { - webAuthnError('general', err.message); + webAuthnError('general', errorMessage(err)); } } @@ -104,7 +105,7 @@ async function login2FA() { await verifyAssertion(credential); } catch (err) { if (!options.publicKey.extensions?.appid) { - webAuthnError('general', err.message); + webAuthnError('general', errorMessage(err)); return; } delete options.publicKey.extensions.appid; @@ -114,7 +115,7 @@ async function login2FA() { }); await verifyAssertion(credential); } catch (err) { - webAuthnError('general', err.message); + webAuthnError('general', errorMessage(err)); } } } @@ -262,6 +263,6 @@ async function webAuthnRegisterRequest() { }); await webauthnRegistered(credential); } catch (err) { - webAuthnError('unknown', err); + webAuthnError('unknown', errorMessage(err)); } } diff --git a/web_src/js/markup/common.ts b/web_src/js/markup/common.ts index e826c8fd863..096d8ddf667 100644 --- a/web_src/js/markup/common.ts +++ b/web_src/js/markup/common.ts @@ -1,8 +1,10 @@ -export function displayError(el: Element, err: Error): void { +import {errorMessage} from '../modules/errors.ts'; + +export function displayError(el: Element, err: unknown): void { el.classList.remove('is-loading'); const errorNode = document.createElement('pre'); errorNode.setAttribute('class', 'ui message error markup-block-error'); - errorNode.textContent = err.message || String(err); + errorNode.textContent = errorMessage(err); el.before(errorNode); el.setAttribute('data-render-done', 'true'); } diff --git a/web_src/js/markup/render-iframe.ts b/web_src/js/markup/render-iframe.ts index 2b1b06e5c0b..f05523943a8 100644 --- a/web_src/js/markup/render-iframe.ts +++ b/web_src/js/markup/render-iframe.ts @@ -1,4 +1,5 @@ import {generateElemId} from '../utils/dom.ts'; +import {errorMessage} from '../modules/errors.ts'; import {isDarkTheme} from '../utils.ts'; import {GET} from '../modules/fetch.ts'; @@ -11,7 +12,7 @@ function safeRenderIframeLink(link: any): string | null { } return url.href; } catch (e) { - console.error(`Failed to parse link: ${link}, error: ${e}`); + console.error(`Failed to parse link: ${link}, error: ${errorMessage(e)}`); return null; } } diff --git a/web_src/js/modules/errors.ts b/web_src/js/modules/errors.ts index ddda0ebe42b..276b498ead7 100644 --- a/web_src/js/modules/errors.ts +++ b/web_src/js/modules/errors.ts @@ -2,6 +2,10 @@ import {html} from '../utils/html.ts'; import type {Intent} from '../types.ts'; +export function errorMessage(err: unknown): string { + return (err as Error)?.message || String(err); +} + export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { const msgContainer = document.querySelector('.page-content') ?? document.body; if (!msgContainer) { diff --git a/web_src/js/utils/match.ts b/web_src/js/utils/match.ts index f364481f468..41333d9fa46 100644 --- a/web_src/js/utils/match.ts +++ b/web_src/js/utils/match.ts @@ -2,6 +2,7 @@ import emojis from '../../../assets/emoji.json' with {type: 'json'}; import {GET} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {parseIssuePageInfo} from '../utils.ts'; +import {errorMessage} from '../modules/errors.ts'; import type {Issue, Mention} from '../types.ts'; const maxMatches = 6; @@ -47,7 +48,7 @@ export function fetchMentions(mentionsUrl: string): Promise { if (!res.ok) throw new Error(res.statusText); return await res.json() as Mention[]; } catch (e) { - showErrorToast(`Failed to load mentions: ${e}`); + showErrorToast(`Failed to load mentions: ${errorMessage(e)}`); return []; } })(); diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index 879ce36e6e3..b20d11e2433 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -3,13 +3,13 @@ import {addDelegatedEventListener, generateElemId, isDocumentFragmentOrElementNo import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { - popup: HTMLDivElement; - overflowItems: Array; - button: HTMLButtonElement | null; - menuItemsEl: HTMLElement; - resizeObserver: ResizeObserver; - mutationObserver: MutationObserver; - lastWidth: number; + popup!: HTMLDivElement; + overflowItems: Array = []; + button: HTMLButtonElement | null = null; + menuItemsEl!: HTMLElement; + resizeObserver!: ResizeObserver; + mutationObserver!: MutationObserver; + lastWidth!: number; updateButtonActivationState() { if (!this.button || !this.popup) return; @@ -100,7 +100,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const itemOverFlowMenuButton = this.querySelector('.overflow-menu-button'); // move items in popup back into the menu items for subsequent measurement - for (const item of this.overflowItems || []) { + for (const item of this.overflowItems) { if (!itemFlexSpace || item.getAttribute('data-after-flex-space')) { this.menuItemsEl.append(item); } else {