mirror of
https://github.com/go-gitea/gitea
synced 2024-12-26 11:44:39 +01:00
Fix remaining typescript issues, enable tsc
(#32840)
Fixes 79 typescript errors. Discovered at least two bugs in `notifications.ts`, and I'm pretty sure this feature was at least partially broken and may still be, I don't really know how to test it. After this, only like ~10 typescript errors remain in the codebase but those are harder to solve. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
74b06d4f5c
commit
c8ea41b049
24 changed files with 152 additions and 134 deletions
8
Makefile
8
Makefile
|
@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
|
|||
.PHONY: lint-js
|
||||
lint-js: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
||||
# npx vue-tsc
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-js-fix
|
||||
lint-js-fix: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
||||
# npx vue-tsc
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-css
|
||||
lint-css: node_modules
|
||||
|
@ -451,10 +451,6 @@ lint-templates: .venv node_modules
|
|||
lint-yaml: .venv
|
||||
@poetry run yamllint .
|
||||
|
||||
.PHONY: tsc
|
||||
tsc:
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: watch
|
||||
watch:
|
||||
@bash tools/watch.sh
|
||||
|
|
62
package-lock.json
generated
62
package-lock.json
generated
|
@ -67,6 +67,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
|
||||
"@playwright/test": "1.49.0",
|
||||
"@silverwind/vue-tsc": "2.1.13",
|
||||
"@stoplight/spectral-cli": "6.14.2",
|
||||
"@stylistic/eslint-plugin-js": "2.11.0",
|
||||
"@stylistic/stylelint-plugin": "3.1.1",
|
||||
|
@ -111,8 +112,7 @@
|
|||
"type-fest": "4.30.0",
|
||||
"updates": "16.4.0",
|
||||
"vite-string-plugin": "1.3.4",
|
||||
"vitest": "2.1.8",
|
||||
"vue-tsc": "2.1.10"
|
||||
"vitest": "2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
|
@ -3833,6 +3833,24 @@
|
|||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@silverwind/vue-tsc": {
|
||||
"version": "2.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@silverwind/vue-tsc/-/vue-tsc-2.1.13.tgz",
|
||||
"integrity": "sha512-ejFxz1KZiUGAESbC+eURnjqt0N95qkU9eZU7W15wgF9zV+v2FEu3ZLduuXTC7D/Sg6lL1R/QjPfUbxbAbBQOsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "~2.4.11",
|
||||
"@vue/language-core": "2.1.10",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@silverwind/vue3-calendar-heatmap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz",
|
||||
|
@ -5335,30 +5353,30 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz",
|
||||
"integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==",
|
||||
"version": "2.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz",
|
||||
"integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.10"
|
||||
"@volar/source-map": "2.4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz",
|
||||
"integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==",
|
||||
"version": "2.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz",
|
||||
"integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz",
|
||||
"integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==",
|
||||
"version": "2.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz",
|
||||
"integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.10",
|
||||
"@volar/language-core": "2.4.11",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
|
@ -15780,24 +15798,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "2.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz",
|
||||
"integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "~2.4.8",
|
||||
"@vue/language-core": "2.1.10",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
|
||||
"@playwright/test": "1.49.0",
|
||||
"@silverwind/vue-tsc": "2.1.13",
|
||||
"@stoplight/spectral-cli": "6.14.2",
|
||||
"@stylistic/eslint-plugin-js": "2.11.0",
|
||||
"@stylistic/stylelint-plugin": "3.1.1",
|
||||
|
@ -110,8 +111,7 @@
|
|||
"type-fest": "4.30.0",
|
||||
"updates": "16.4.0",
|
||||
"vite-string-plugin": "1.3.4",
|
||||
"vitest": "2.1.8",
|
||||
"vue-tsc": "2.1.10"
|
||||
"vitest": "2.1.8"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
],
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "nodenext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowJs": true,
|
||||
|
|
|
@ -7,7 +7,7 @@ const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
|
|||
const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/; // eg: "{owner}/{repo}#{index}"
|
||||
|
||||
// if the searchText can be parsed to an "issue goto link", return the link, otherwise return empty string
|
||||
export function parseIssueListQuickGotoLink(repoLink, searchText) {
|
||||
export function parseIssueListQuickGotoLink(repoLink: string, searchText: string) {
|
||||
searchText = searchText.trim();
|
||||
let targetUrl = '';
|
||||
if (repoLink) {
|
||||
|
@ -15,13 +15,12 @@ export function parseIssueListQuickGotoLink(repoLink, searchText) {
|
|||
if (reIssueIndex.test(searchText)) {
|
||||
targetUrl = `${repoLink}/issues/${searchText}`;
|
||||
} else if (reIssueSharpIndex.test(searchText)) {
|
||||
targetUrl = `${repoLink}/issues/${searchText.substr(1)}`;
|
||||
targetUrl = `${repoLink}/issues/${searchText.substring(1)}`;
|
||||
}
|
||||
} else {
|
||||
// try to parse it for a global search (eg: "owner/repo#123")
|
||||
const matchIssueOwnerRepoIndex = searchText.match(reIssueOwnerRepoIndex);
|
||||
if (matchIssueOwnerRepoIndex) {
|
||||
const [_, owner, repo, index] = matchIssueOwnerRepoIndex;
|
||||
const [_, owner, repo, index] = reIssueOwnerRepoIndex.exec(searchText) || [];
|
||||
if (owner) {
|
||||
targetUrl = `${appSubUrl}/${owner}/${repo}/issues/${index}`;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +32,7 @@ export function initCommonIssueListQuickGoto() {
|
|||
if (!goto) return;
|
||||
|
||||
const form = goto.closest('form');
|
||||
const input = form.querySelector('input[name=q]');
|
||||
const input = form.querySelector<HTMLInputElement>('input[name=q]');
|
||||
const repoLink = goto.getAttribute('data-repo-link');
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
|
|
|
@ -283,8 +283,8 @@ export class ComboMarkdownEditor {
|
|||
];
|
||||
}
|
||||
|
||||
parseEasyMDEToolbar(EasyMDE, actions) {
|
||||
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(EasyMDE, this);
|
||||
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) {
|
||||
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
|
||||
const processed = [];
|
||||
for (const action of actions) {
|
||||
const actionButton = this.easyMDEToolbarActions[action];
|
||||
|
|
|
@ -1,100 +1,102 @@
|
|||
import {svg} from '../../svg.ts';
|
||||
import type EasyMDE from 'easymde';
|
||||
import type {ComboMarkdownEditor} from './ComboMarkdownEditor.ts';
|
||||
|
||||
export function easyMDEToolbarActions(EasyMDE, editor) {
|
||||
const actions = {
|
||||
export function easyMDEToolbarActions(easyMde: typeof EasyMDE, editor: ComboMarkdownEditor): Record<string, Partial<EasyMDE.ToolbarIcon | string>> {
|
||||
const actions: Record<string, Partial<EasyMDE.ToolbarIcon> | string> = {
|
||||
'|': '|',
|
||||
'heading-1': {
|
||||
action: EasyMDE.toggleHeading1,
|
||||
action: easyMde.toggleHeading1,
|
||||
icon: svg('octicon-heading'),
|
||||
title: 'Heading 1',
|
||||
},
|
||||
'heading-2': {
|
||||
action: EasyMDE.toggleHeading2,
|
||||
action: easyMde.toggleHeading2,
|
||||
icon: svg('octicon-heading'),
|
||||
title: 'Heading 2',
|
||||
},
|
||||
'heading-3': {
|
||||
action: EasyMDE.toggleHeading3,
|
||||
action: easyMde.toggleHeading3,
|
||||
icon: svg('octicon-heading'),
|
||||
title: 'Heading 3',
|
||||
},
|
||||
'heading-smaller': {
|
||||
action: EasyMDE.toggleHeadingSmaller,
|
||||
action: easyMde.toggleHeadingSmaller,
|
||||
icon: svg('octicon-heading'),
|
||||
title: 'Decrease Heading',
|
||||
},
|
||||
'heading-bigger': {
|
||||
action: EasyMDE.toggleHeadingBigger,
|
||||
action: easyMde.toggleHeadingBigger,
|
||||
icon: svg('octicon-heading'),
|
||||
title: 'Increase Heading',
|
||||
},
|
||||
'bold': {
|
||||
action: EasyMDE.toggleBold,
|
||||
action: easyMde.toggleBold,
|
||||
icon: svg('octicon-bold'),
|
||||
title: 'Bold',
|
||||
},
|
||||
'italic': {
|
||||
action: EasyMDE.toggleItalic,
|
||||
action: easyMde.toggleItalic,
|
||||
icon: svg('octicon-italic'),
|
||||
title: 'Italic',
|
||||
},
|
||||
'strikethrough': {
|
||||
action: EasyMDE.toggleStrikethrough,
|
||||
action: easyMde.toggleStrikethrough,
|
||||
icon: svg('octicon-strikethrough'),
|
||||
title: 'Strikethrough',
|
||||
},
|
||||
'quote': {
|
||||
action: EasyMDE.toggleBlockquote,
|
||||
action: easyMde.toggleBlockquote,
|
||||
icon: svg('octicon-quote'),
|
||||
title: 'Quote',
|
||||
},
|
||||
'code': {
|
||||
action: EasyMDE.toggleCodeBlock,
|
||||
action: easyMde.toggleCodeBlock,
|
||||
icon: svg('octicon-code'),
|
||||
title: 'Code',
|
||||
},
|
||||
'link': {
|
||||
action: EasyMDE.drawLink,
|
||||
action: easyMde.drawLink,
|
||||
icon: svg('octicon-link'),
|
||||
title: 'Link',
|
||||
},
|
||||
'unordered-list': {
|
||||
action: EasyMDE.toggleUnorderedList,
|
||||
action: easyMde.toggleUnorderedList,
|
||||
icon: svg('octicon-list-unordered'),
|
||||
title: 'Unordered List',
|
||||
},
|
||||
'ordered-list': {
|
||||
action: EasyMDE.toggleOrderedList,
|
||||
action: easyMde.toggleOrderedList,
|
||||
icon: svg('octicon-list-ordered'),
|
||||
title: 'Ordered List',
|
||||
},
|
||||
'image': {
|
||||
action: EasyMDE.drawImage,
|
||||
action: easyMde.drawImage,
|
||||
icon: svg('octicon-image'),
|
||||
title: 'Image',
|
||||
},
|
||||
'table': {
|
||||
action: EasyMDE.drawTable,
|
||||
action: easyMde.drawTable,
|
||||
icon: svg('octicon-table'),
|
||||
title: 'Table',
|
||||
},
|
||||
'horizontal-rule': {
|
||||
action: EasyMDE.drawHorizontalRule,
|
||||
action: easyMde.drawHorizontalRule,
|
||||
icon: svg('octicon-horizontal-rule'),
|
||||
title: 'Horizontal Rule',
|
||||
},
|
||||
'preview': {
|
||||
action: EasyMDE.togglePreview,
|
||||
action: easyMde.togglePreview,
|
||||
icon: svg('octicon-eye'),
|
||||
title: 'Preview',
|
||||
},
|
||||
'fullscreen': {
|
||||
action: EasyMDE.toggleFullScreen,
|
||||
action: easyMde.toggleFullScreen,
|
||||
icon: svg('octicon-screen-full'),
|
||||
title: 'Fullscreen',
|
||||
},
|
||||
'side-by-side': {
|
||||
action: EasyMDE.toggleSideBySide,
|
||||
action: easyMde.toggleSideBySide,
|
||||
icon: svg('octicon-columns'),
|
||||
title: 'Side by Side',
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
|||
|
||||
export function initCompReactionSelector(parent: ParentNode = document) {
|
||||
for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) {
|
||||
container.addEventListener('click', async (e) => {
|
||||
container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
|
||||
const target = e.target.closest('.comment-reaction-button');
|
||||
if (!target) return;
|
||||
|
|
|
@ -23,7 +23,7 @@ export function initCompWebHookEditor() {
|
|||
}
|
||||
|
||||
// some webhooks (like Gitea) allow to set the request method (GET/POST), and it would toggle the "Content Type" field
|
||||
const httpMethodInput = document.querySelector('#http_method');
|
||||
const httpMethodInput = document.querySelector<HTMLInputElement>('#http_method');
|
||||
if (httpMethodInput) {
|
||||
const updateContentType = function () {
|
||||
const visible = httpMethodInput.value === 'POST';
|
||||
|
|
|
@ -6,6 +6,7 @@ import {GET, POST} from '../modules/fetch.ts';
|
|||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
|
||||
import {isImageFile, isVideoFile} from '../utils.ts';
|
||||
import type {DropzoneFile} from 'dropzone/index.js';
|
||||
|
||||
const {csrfToken, i18n} = window.config;
|
||||
|
||||
|
@ -15,14 +16,14 @@ export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file';
|
|||
export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done';
|
||||
|
||||
async function createDropzone(el, opts) {
|
||||
const [{Dropzone}] = await Promise.all([
|
||||
const [{default: Dropzone}] = await Promise.all([
|
||||
import(/* webpackChunkName: "dropzone" */'dropzone'),
|
||||
import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'),
|
||||
]);
|
||||
return new Dropzone(el, opts);
|
||||
}
|
||||
|
||||
export function generateMarkdownLinkForAttachment(file, {width, dppx} = {}) {
|
||||
export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: number, dppx?: number} = {}) {
|
||||
let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`;
|
||||
if (isImageFile(file)) {
|
||||
fileMarkdown = `!${fileMarkdown}`;
|
||||
|
@ -60,14 +61,14 @@ function addCopyLink(file) {
|
|||
/**
|
||||
* @param {HTMLElement} dropzoneEl
|
||||
*/
|
||||
export async function initDropzone(dropzoneEl) {
|
||||
export async function initDropzone(dropzoneEl: HTMLElement) {
|
||||
const listAttachmentsUrl = dropzoneEl.closest('[data-attachment-url]')?.getAttribute('data-attachment-url');
|
||||
const removeAttachmentUrl = dropzoneEl.getAttribute('data-remove-url');
|
||||
const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url');
|
||||
|
||||
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
|
||||
let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
|
||||
const opts = {
|
||||
const opts: Record<string, any> = {
|
||||
url: dropzoneEl.getAttribute('data-upload-url'),
|
||||
headers: {'X-Csrf-Token': csrfToken},
|
||||
acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')) ? null : dropzoneEl.getAttribute('data-accepts'),
|
||||
|
@ -88,7 +89,7 @@ export async function initDropzone(dropzoneEl) {
|
|||
// "http://localhost:3000/owner/repo/issues/[object%20Event]"
|
||||
// the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">'
|
||||
const dzInst = await createDropzone(dropzoneEl, opts);
|
||||
dzInst.on('success', (file, resp) => {
|
||||
dzInst.on('success', (file: DropzoneFile & {uuid: string}, resp: any) => {
|
||||
file.uuid = resp.uuid;
|
||||
fileUuidDict[file.uuid] = {submitted: false};
|
||||
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid});
|
||||
|
@ -97,7 +98,7 @@ export async function initDropzone(dropzoneEl) {
|
|||
dzInst.emit(DropzoneCustomEventUploadDone, {file});
|
||||
});
|
||||
|
||||
dzInst.on('removedfile', async (file) => {
|
||||
dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => {
|
||||
if (disableRemovedfileEvent) return;
|
||||
|
||||
dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import emojis from '../../../assets/emoji.json';
|
||||
import emojis from '../../../assets/emoji.json' with {type: 'json'};
|
||||
|
||||
const {assetUrlPrefix, customEmojis} = window.config;
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@ const sourcesByUrl = {};
|
|||
const sourcesByPort = {};
|
||||
|
||||
class Source {
|
||||
url: string;
|
||||
eventSource: EventSource;
|
||||
listening: Record<string, any>;
|
||||
clients: Array<any>;
|
||||
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.eventSource = new EventSource(url);
|
||||
|
@ -67,7 +72,7 @@ class Source {
|
|||
}
|
||||
}
|
||||
|
||||
self.addEventListener('connect', (e) => {
|
||||
self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||
for (const port of e.ports) {
|
||||
port.addEventListener('message', (event) => {
|
||||
if (!self.EventSource) {
|
||||
|
|
|
@ -21,8 +21,8 @@ export function initHeatmap() {
|
|||
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
|
||||
const locale = {
|
||||
heatMapLocale: {
|
||||
months: new Array(12).fill().map((_, idx) => translateMonth(idx)),
|
||||
days: new Array(7).fill().map((_, idx) => translateDay(idx)),
|
||||
months: new Array(12).fill(undefined).map((_, idx) => translateMonth(idx)),
|
||||
days: new Array(7).fill(undefined).map((_, idx) => translateDay(idx)),
|
||||
on: ' - ', // no correct locale support for it, because in many languages the sentence is not "something on someday"
|
||||
more: el.getAttribute('data-locale-more'),
|
||||
less: el.getAttribute('data-locale-less'),
|
||||
|
|
|
@ -22,9 +22,9 @@ function initPreInstall() {
|
|||
mssql: '127.0.0.1:1433',
|
||||
};
|
||||
|
||||
const dbHost = document.querySelector('#db_host');
|
||||
const dbUser = document.querySelector('#db_user');
|
||||
const dbName = document.querySelector('#db_name');
|
||||
const dbHost = document.querySelector<HTMLInputElement>('#db_host');
|
||||
const dbUser = document.querySelector<HTMLInputElement>('#db_user');
|
||||
const dbName = document.querySelector<HTMLInputElement>('#db_name');
|
||||
|
||||
// Database type change detection.
|
||||
document.querySelector('#db_type').addEventListener('change', function () {
|
||||
|
@ -48,12 +48,12 @@ function initPreInstall() {
|
|||
});
|
||||
document.querySelector('#db_type').dispatchEvent(new Event('change'));
|
||||
|
||||
const appUrl = document.querySelector('#app_url');
|
||||
const appUrl = document.querySelector<HTMLInputElement>('#app_url');
|
||||
if (appUrl.value.includes('://localhost')) {
|
||||
appUrl.value = window.location.href;
|
||||
}
|
||||
|
||||
const domain = document.querySelector('#domain');
|
||||
const domain = document.querySelector<HTMLInputElement>('#domain');
|
||||
if (domain.value.trim() === 'localhost') {
|
||||
domain.value = window.location.hostname;
|
||||
}
|
||||
|
@ -61,43 +61,43 @@ function initPreInstall() {
|
|||
// TODO: better handling of exclusive relations.
|
||||
document.querySelector('#offline-mode input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
document.querySelector('#disable-gravatar input').checked = true;
|
||||
document.querySelector('#federated-avatar-lookup input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true;
|
||||
document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false;
|
||||
}
|
||||
});
|
||||
document.querySelector('#disable-gravatar input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
document.querySelector('#federated-avatar-lookup input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false;
|
||||
} else {
|
||||
document.querySelector('#offline-mode input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#offline-mode input').checked = false;
|
||||
}
|
||||
});
|
||||
document.querySelector('#federated-avatar-lookup input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
document.querySelector('#disable-gravatar input').checked = false;
|
||||
document.querySelector('#offline-mode input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#offline-mode input').checked = false;
|
||||
}
|
||||
});
|
||||
document.querySelector('#enable-openid-signin input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
if (!document.querySelector('#disable-registration input').checked) {
|
||||
document.querySelector('#enable-openid-signup input').checked = true;
|
||||
if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) {
|
||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true;
|
||||
}
|
||||
} else {
|
||||
document.querySelector('#enable-openid-signup input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false;
|
||||
}
|
||||
});
|
||||
document.querySelector('#disable-registration input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
document.querySelector('#enable-captcha input').checked = false;
|
||||
document.querySelector('#enable-openid-signup input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false;
|
||||
} else {
|
||||
document.querySelector('#enable-openid-signup input').checked = true;
|
||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true;
|
||||
}
|
||||
});
|
||||
document.querySelector('#enable-captcha input').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
document.querySelector('#disable-registration input').checked = false;
|
||||
document.querySelector<HTMLInputElement>('#disable-registration input').checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,25 +14,25 @@ export function initNotificationsTable() {
|
|||
window.addEventListener('pageshow', (e) => {
|
||||
if (e.persisted) { // page was restored from bfcache
|
||||
const table = document.querySelector('#notification_table');
|
||||
const unreadCountEl = document.querySelector('.notifications-unread-count');
|
||||
const unreadCountEl = document.querySelector<HTMLElement>('.notifications-unread-count');
|
||||
let unreadCount = parseInt(unreadCountEl.textContent);
|
||||
for (const item of table.querySelectorAll('.notifications-item[data-remove="true"]')) {
|
||||
item.remove();
|
||||
unreadCount -= 1;
|
||||
}
|
||||
unreadCountEl.textContent = unreadCount;
|
||||
unreadCountEl.textContent = String(unreadCount);
|
||||
}
|
||||
});
|
||||
|
||||
// mark clicked unread links for deletion on bfcache restore
|
||||
for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) {
|
||||
link.addEventListener('click', (e) => {
|
||||
link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => {
|
||||
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function receiveUpdateCount(event) {
|
||||
async function receiveUpdateCount(event: MessageEvent) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
|
@ -50,7 +50,7 @@ export function initNotificationCount() {
|
|||
if (!document.querySelector('.notification_count')) return;
|
||||
|
||||
let usingPeriodicPoller = false;
|
||||
const startPeriodicPoller = (timeout, lastCount) => {
|
||||
const startPeriodicPoller = (timeout: number, lastCount?: number) => {
|
||||
if (timeout <= 0 || !Number.isFinite(timeout)) return;
|
||||
usingPeriodicPoller = true;
|
||||
lastCount = lastCount ?? getCurrentCount();
|
||||
|
@ -72,13 +72,13 @@ export function initNotificationCount() {
|
|||
type: 'start',
|
||||
url: `${window.location.origin}${appSubUrl}/user/events`,
|
||||
});
|
||||
worker.port.addEventListener('message', (event) => {
|
||||
worker.port.addEventListener('message', (event: MessageEvent) => {
|
||||
if (!event.data || !event.data.type) {
|
||||
console.error('unknown worker message event', event);
|
||||
return;
|
||||
}
|
||||
if (event.data.type === 'notification-count') {
|
||||
const _promise = receiveUpdateCount(event.data);
|
||||
receiveUpdateCount(event); // no await
|
||||
} else if (event.data.type === 'no-event-source') {
|
||||
// browser doesn't support EventSource, falling back to periodic poller
|
||||
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
|
||||
|
@ -118,10 +118,10 @@ export function initNotificationCount() {
|
|||
}
|
||||
|
||||
function getCurrentCount() {
|
||||
return document.querySelector('.notification_count').textContent;
|
||||
return Number(document.querySelector('.notification_count').textContent ?? '0');
|
||||
}
|
||||
|
||||
async function updateNotificationCountWithCallback(callback, timeout, lastCount) {
|
||||
async function updateNotificationCountWithCallback(callback: (timeout: number, newCount: number) => void, timeout: number, lastCount: number) {
|
||||
const currentCount = getCurrentCount();
|
||||
if (lastCount !== currentCount) {
|
||||
callback(notificationSettings.MinTimeout, currentCount);
|
||||
|
@ -149,10 +149,9 @@ async function updateNotificationTable() {
|
|||
if (notificationDiv) {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('div-only', true);
|
||||
params.set('sequence-number', ++notificationSequenceNumber);
|
||||
const url = `${appSubUrl}/notifications?${params.toString()}`;
|
||||
const response = await GET(url);
|
||||
params.set('div-only', String(true));
|
||||
params.set('sequence-number', String(++notificationSequenceNumber));
|
||||
const response = await GET(`${appSubUrl}/notifications?${params.toString()}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch notification table');
|
||||
|
@ -169,7 +168,7 @@ async function updateNotificationTable() {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateNotificationCount() {
|
||||
async function updateNotificationCount(): Promise<number> {
|
||||
try {
|
||||
const response = await GET(`${appSubUrl}/notifications/new`);
|
||||
|
||||
|
@ -185,9 +184,9 @@ async function updateNotificationCount() {
|
|||
el.textContent = `${data.new}`;
|
||||
}
|
||||
|
||||
return `${data.new}`;
|
||||
return data.new as number;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return '0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export function initOAuth2SettingsDisableCheckbox() {
|
||||
for (const e of document.querySelectorAll('.disable-setting')) e.addEventListener('change', ({target}) => {
|
||||
document.querySelector(e.getAttribute('data-target')).classList.toggle('disabled', target.checked);
|
||||
});
|
||||
for (const el of document.querySelectorAll('.disable-setting')) {
|
||||
el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => {
|
||||
document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export function countAndUpdateViewedFiles() {
|
|||
export function initViewedCheckboxListenerFor() {
|
||||
for (const form of document.querySelectorAll(`${viewedCheckboxSelector}:not([data-has-viewed-checkbox-listener="true"])`)) {
|
||||
// To prevent double addition of listeners
|
||||
form.setAttribute('data-has-viewed-checkbox-listener', true);
|
||||
form.setAttribute('data-has-viewed-checkbox-listener', String(true));
|
||||
|
||||
// The checkbox consists of a div containing the real checkbox with its label and the CSRF token,
|
||||
// hence the actual checkbox first has to be found
|
||||
|
@ -67,7 +67,7 @@ export function initViewedCheckboxListenerFor() {
|
|||
// Unfortunately, actual forms cause too many problems, hence another approach is needed
|
||||
const files = {};
|
||||
files[fileName] = this.checked;
|
||||
const data = {files};
|
||||
const data: Record<string, any> = {files};
|
||||
const headCommitSHA = form.getAttribute('data-headcommit');
|
||||
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
|
||||
POST(form.getAttribute('data-link'), {data});
|
||||
|
|
|
@ -35,7 +35,7 @@ function initEditPreviewTab(elForm: HTMLFormElement) {
|
|||
}
|
||||
|
||||
export function initRepoEditor() {
|
||||
const dropzoneUpload = document.querySelector('.page-content.repository.editor.upload .dropzone');
|
||||
const dropzoneUpload = document.querySelector<HTMLElement>('.page-content.repository.editor.upload .dropzone');
|
||||
if (dropzoneUpload) initDropzone(dropzoneUpload);
|
||||
|
||||
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
||||
|
|
|
@ -5,9 +5,10 @@ export function initRepositorySearch() {
|
|||
repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(repositorySearchForm);
|
||||
const params = new URLSearchParams(formData);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of new FormData(repositorySearchForm).entries()) {
|
||||
params.set(key, value.toString());
|
||||
}
|
||||
if (e.target.name === 'clear-filter') {
|
||||
params.delete('archived');
|
||||
params.delete('fork');
|
||||
|
|
|
@ -2,6 +2,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest';
|
|||
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
import type {SortableEvent} from 'sortablejs';
|
||||
|
||||
vi.mock('../modules/fetch.ts', () => ({
|
||||
POST: vi.fn(),
|
||||
|
@ -54,8 +55,8 @@ describe('Repository Branch Settings', () => {
|
|||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||
|
||||
// Mock createSortable to capture and execute the onEnd callback
|
||||
vi.mocked(createSortable).mockImplementation((_el, options) => {
|
||||
options.onEnd();
|
||||
vi.mocked(createSortable).mockImplementation(async (_el: Element, options) => {
|
||||
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
||||
return {destroy: vi.fn()};
|
||||
});
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ function makeCollections({mentions, emoji}) {
|
|||
export async function attachTribute(element, {mentions, emoji}) {
|
||||
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
|
||||
const collections = makeCollections({mentions, emoji});
|
||||
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
|
||||
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
|
||||
tribute.attach(element);
|
||||
return tribute;
|
||||
|
|
15
web_src/js/globals.d.ts
vendored
15
web_src/js/globals.d.ts
vendored
|
@ -8,6 +8,17 @@ declare module '*.css' {
|
|||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.vue' {
|
||||
import type {DefineComponent} from 'vue';
|
||||
const component: DefineComponent<unknown, unknown, any>;
|
||||
export default component;
|
||||
// List of named exports from vue components, used to make `tsc` output clean.
|
||||
// To actually lint .vue files, `vue-tsc` is used because `tsc` can not parse them.
|
||||
export function initRepoBranchTagSelector(selector: string): void;
|
||||
export function initDashboardRepoList(): void;
|
||||
export function initRepositoryActionView(): void;
|
||||
}
|
||||
|
||||
declare let __webpack_public_path__: string;
|
||||
|
||||
declare module 'htmx.org/dist/htmx.esm.js' {
|
||||
|
@ -16,8 +27,8 @@ declare module 'htmx.org/dist/htmx.esm.js' {
|
|||
}
|
||||
|
||||
declare module 'uint8-to-base64' {
|
||||
export function encode(arrayBuffer: ArrayBuffer): string;
|
||||
export function decode(base64str: string): ArrayBuffer;
|
||||
export function encode(arrayBuffer: Uint8Array): string;
|
||||
export function decode(base64str: string): Uint8Array;
|
||||
}
|
||||
|
||||
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
|
||||
|
|
|
@ -16,7 +16,6 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
|
|||
// because we should use our own wrapper functions to handle them, do not let the user override them
|
||||
const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
|
||||
|
||||
// @ts-expect-error: wrong type derived by typescript
|
||||
const instance: Instance = tippy(target, {
|
||||
appendTo: document.body,
|
||||
animation: false,
|
||||
|
|
|
@ -134,16 +134,16 @@ export function toAbsoluteUrl(url: string): string {
|
|||
return `${window.location.origin}${url}`;
|
||||
}
|
||||
|
||||
// Encode an ArrayBuffer into a URLEncoded base64 string.
|
||||
export function encodeURLEncodedBase64(arrayBuffer: ArrayBuffer): string {
|
||||
return encode(arrayBuffer)
|
||||
// Encode an Uint8Array into a URLEncoded base64 string.
|
||||
export function encodeURLEncodedBase64(uint8Array: Uint8Array): string {
|
||||
return encode(uint8Array)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
}
|
||||
|
||||
// Decode a URLEncoded base64 to an ArrayBuffer.
|
||||
export function decodeURLEncodedBase64(base64url: string): ArrayBuffer {
|
||||
// Decode a URLEncoded base64 to an Uint8Array.
|
||||
export function decodeURLEncodedBase64(base64url: string): Uint8Array {
|
||||
return decode(base64url
|
||||
.replace(/_/g, '/')
|
||||
.replace(/-/g, '+'));
|
||||
|
|
Loading…
Reference in a new issue