0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-14 13:08:11 +02:00

Merge branch 'main' into feature/add-api-workflowruns

This commit is contained in:
silverwind 2026-04-21 00:54:12 +02:00 committed by GitHub
commit 65473f8e6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 873 additions and 709 deletions

View File

@ -38,7 +38,7 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.0.0
- uses: astral-sh/setup-uv@v8.1.0
- run: uv python install 3.14
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v6
@ -58,7 +58,7 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.0.0
- uses: astral-sh/setup-uv@v8.1.0
- run: uv python install 3.14
- run: make deps-py
- run: make lint-yaml
@ -72,9 +72,11 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make lint-json

View File

@ -518,7 +518,8 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
playwright: deps-frontend
@pnpm exec playwright install --with-deps chromium firefox webkit $(PLAYWRIGHT_FLAGS)
@# on GitHub Actions VMs, playwright's system deps are pre-installed
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e
test-e2e: playwright $(EXECUTABLE_E2E)

20
go.mod
View File

@ -27,8 +27,8 @@ require (
github.com/PuerkitoBio/goquery v1.12.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/aws/aws-sdk-go-v2/credentials v1.19.14
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12
github.com/aws/aws-sdk-go-v2/credentials v1.19.15
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.13
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.7
github.com/bohde/codel v0.2.0
@ -37,7 +37,7 @@ require (
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/dlclark/regexp2 v1.11.5
github.com/dlclark/regexp2 v1.12.0
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
@ -49,14 +49,14 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron/v2 v2.20.0
github.com/go-co-op/gocron/v2 v2.21.0
github.com/go-enry/go-enry/v2 v2.9.6
github.com/go-git/go-billy/v5 v5.8.0
github.com/go-git/go-git/v5 v5.18.0
github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-redsync/redsync/v4 v4.16.0
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.16.4
github.com/go-webauthn/webauthn v0.16.5
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.1
@ -139,10 +139,10 @@ require (
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect
github.com/aws/smithy-go v1.25.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
@ -258,7 +258,7 @@ require (
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.6.3 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect

40
go.sum
View File

@ -92,18 +92,18 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12 h1:yv3mfWt/eiDTTry6fkN5hh8wHJfU5ygnw+DJp10C0/c=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12/go.mod h1:voO3LP/dZ4CTERiNWCz3DFLbK/8hbfeC1OJkLW+sang=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg=
github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ=
github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4=
github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.13 h1:IIW5QmNI9PrnDTBCPa75HcD0g+hoD/a+d388dbIAkEM=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.13/go.mod h1:B1FUSufCQp3d8VUzob1EY+AdSo2OuEWNEY6GQff7EHA=
github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U=
github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -239,8 +239,8 @@ github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2r
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@ -288,8 +288,8 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron/v2 v2.20.0 h1:9IMrnnVSWjfSh3E54gWmWCHbloQJLh6f9+nwyKfLNpc=
github.com/go-co-op/gocron/v2 v2.20.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
github.com/go-co-op/gocron/v2 v2.21.0 h1:e1nt9AEFglarRH9/9y9q0V5sblwxlknpHPjttEajrwQ=
github.com/go-co-op/gocron/v2 v2.21.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs=
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
@ -325,8 +325,8 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-webauthn/webauthn v0.16.4 h1:R9jqR/cYZa7hRquFF7Za/8qoH/K/TIs1/Q/4CyGN+1Q=
github.com/go-webauthn/webauthn v0.16.4/go.mod h1:SU2ljAgToTV/YLPI0C05QS4qn+e04WpB5g1RMfcZfS4=
github.com/go-webauthn/webauthn v0.16.5 h1:x+vADHlaiIjta23kGhtwyCIlB5mayKx6SBlpwQ5NF9A=
github.com/go-webauthn/webauthn v0.16.5/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
@ -695,8 +695,8 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKN
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=

View File

@ -47,7 +47,7 @@ func ParseJSONError(buf []byte) (ret struct {
}
func ParseJSONRedirect(buf []byte) (ret struct {
Redirect string `json:"redirect"`
Redirect *string `json:"redirect"`
},
) {
_ = json.Unmarshal(buf, &ret)

View File

@ -20,7 +20,7 @@
"@codemirror/lint": "6.9.5",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.41.0",
"@codemirror/view": "6.41.1",
"@deltablot/dropzone": "7.4.3",
"@github/markdown-toolbar-element": "2.2.3",
"@github/paste-markdown": "1.5.3",
@ -28,20 +28,19 @@
"@lezer/highlight": "1.2.3",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@mermaid-js/layout-elk": "0.2.1",
"@primer/octicons": "19.24.0",
"@primer/octicons": "19.24.1",
"@replit/codemirror-indentation-markers": "6.5.3",
"@replit/codemirror-lang-nix": "6.0.1",
"@replit/codemirror-lang-svelte": "6.0.0",
"@replit/codemirror-vscode-keymap": "6.0.2",
"@resvg/resvg-wasm": "2.6.2",
"@silverwind/vue3-calendar-heatmap": "2.1.1",
"@vitejs/plugin-vue": "6.0.6",
"ansi_up": "6.0.6",
"asciinema-player": "3.15.1",
"chart.js": "4.5.1",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.10",
"clippie": "4.1.14",
"codemirror-lang-elixir": "4.0.1",
"colord": "2.9.3",
"compare-versions": "6.1.1",
@ -57,10 +56,10 @@
"online-3d-viewer": "0.18.0",
"pdfobject": "2.3.1",
"perfect-debounce": "2.1.0",
"postcss": "8.5.9",
"rolldown-license-plugin": "2.2.5",
"postcss": "8.5.10",
"rolldown-license-plugin": "3.0.1",
"sortablejs": "1.15.7",
"swagger-ui-dist": "5.32.2",
"swagger-ui-dist": "5.32.4",
"tailwindcss": "3.4.19",
"throttle-debounce": "5.0.2",
"tippy.js": "6.3.7",
@ -68,7 +67,7 @@
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
"vite": "8.0.8",
"vite": "8.0.9",
"vite-string-plugin": "2.0.2",
"vue": "3.5.32",
"vue-bar-graph": "2.2.0",
@ -90,41 +89,41 @@
"@types/swagger-ui-dist": "3.30.6",
"@types/throttle-debounce": "5.0.2",
"@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/parser": "8.59.0",
"@vitejs/plugin-vue": "6.0.6",
"@vitest/eslint-plugin": "1.6.15",
"eslint": "10.2.0",
"@vitest/eslint-plugin": "1.6.16",
"eslint": "10.2.1",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "5.1.1",
"eslint-plugin-de-morgan": "2.1.1",
"eslint-plugin-github": "6.0.0",
"eslint-plugin-import-x": "4.16.2",
"eslint-plugin-playwright": "2.10.1",
"eslint-plugin-playwright": "2.10.2",
"eslint-plugin-regexp": "3.1.0",
"eslint-plugin-sonarjs": "4.0.2",
"eslint-plugin-sonarjs": "4.0.3",
"eslint-plugin-unicorn": "64.0.0",
"eslint-plugin-vue": "10.8.0",
"eslint-plugin-vue-scoped-css": "3.0.0",
"eslint-plugin-wc": "3.1.0",
"globals": "17.5.0",
"happy-dom": "20.8.9",
"happy-dom": "20.9.0",
"jiti": "2.6.1",
"markdownlint-cli": "0.48.0",
"material-icon-theme": "5.33.1",
"nolyfill": "1.0.44",
"postcss-html": "1.8.1",
"spectral-cli-bundle": "1.0.7",
"stylelint": "17.7.0",
"stylelint": "17.8.0",
"stylelint-config-recommended": "18.0.0",
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
"stylelint-declaration-strict-value": "1.11.1",
"stylelint-value-no-unknown-custom-properties": "6.1.1",
"svgo": "4.0.1",
"typescript": "6.0.2",
"typescript-eslint": "8.58.2",
"updates": "17.15.3",
"typescript": "6.0.3",
"typescript-eslint": "8.59.0",
"updates": "17.15.5",
"vitest": "4.1.4",
"vue-tsc": "3.2.6"
"vue-tsc": "3.2.7"
},
"pnpm": {
"peerDependencyRules": {

View File

@ -36,11 +36,5 @@ export default defineConfig({
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
],
});

1160
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -725,16 +725,16 @@ func handleSettingsPostConvert(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
if !ctx.Repo.IsOwner() {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
if repo.Name != form.RepoName {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}
if !repo.IsMirror {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
repo.IsMirror = false
@ -748,14 +748,14 @@ func handleSettingsPostConvert(ctx *context.Context) {
}
log.Trace("Repository converted from mirror to regular: %s", repo.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
ctx.Redirect(repo.Link())
ctx.JSONRedirect(repo.Link())
}
func handleSettingsPostConvertFork(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
if !ctx.Repo.IsOwner() {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
if err := repo.LoadOwner(ctx); err != nil {
@ -763,12 +763,12 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
return
}
if repo.Name != form.RepoName {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}
if !repo.IsFork {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
@ -776,7 +776,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
ctx.Flash.Error(msg)
ctx.Redirect(repo.Link() + "/settings")
ctx.JSONRedirect(repo.Link() + "/settings")
return
}
@ -788,25 +788,25 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
log.Trace("Repository converted from fork to regular: %s", repo.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed"))
ctx.Redirect(repo.Link())
ctx.JSONRedirect(repo.Link())
}
func handleSettingsPostTransfer(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
if !ctx.Repo.IsOwner() {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
if repo.Name != form.RepoName {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}
newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_owner_name"))
return
}
ctx.ServerError("IsUserExist", err)
@ -816,7 +816,7 @@ func handleSettingsPostTransfer(ctx *context.Context) {
if newOwner.Type == user_model.UserTypeOrganization {
if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) {
// The user shouldn't know about this organization
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_owner_name"))
return
}
}
@ -830,14 +830,14 @@ func handleSettingsPostTransfer(ctx *context.Context) {
oldFullname := repo.FullName()
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
} else if repo_model.IsErrRepoTransferInProgress(err) {
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("repo.settings.transfer_in_progress"))
} else if repo_service.IsRepositoryLimitReached(err) {
limit := err.(repo_service.LimitReachedError).Limit
ctx.RenderWithErrDeprecated(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil)
ctx.JSONError(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit))
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("repo.settings.transfer.blocked_user"))
} else {
ctx.ServerError("TransferOwnership", err)
}
@ -852,7 +852,7 @@ func handleSettingsPostTransfer(ctx *context.Context) {
log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed"))
}
ctx.Redirect(repo.Link() + "/settings")
ctx.JSONRedirect(repo.Link() + "/settings")
}
func handleSettingsPostCancelTransfer(ctx *context.Context) {
@ -887,11 +887,11 @@ func handleSettingsPostDelete(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
if !ctx.Repo.IsOwner() {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
if repo.Name != form.RepoName {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}
@ -907,18 +907,18 @@ func handleSettingsPostDelete(ctx *context.Context) {
log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.Redirect(ctx.Repo.Owner.DashboardLink())
ctx.JSONRedirect(ctx.Repo.Owner.DashboardLink())
}
func handleSettingsPostDeleteWiki(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
if !ctx.Repo.IsOwner() {
ctx.HTTPError(http.StatusNotFound)
ctx.JSONErrorNotFound()
return
}
if repo.Name != form.RepoName {
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
return
}
@ -929,7 +929,7 @@ func handleSettingsPostDeleteWiki(ctx *context.Context) {
log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings")
}
func handleSettingsPostArchive(ctx *context.Context) {

View File

@ -111,7 +111,7 @@ func ListTasks() TaskTable {
spec = tags[1] // the second tag is the task spec
}
next, _ = e.NextRun()
prev, _ = e.LastRun()
prev, _ = e.LastRunStartedAt()
}
task.lock.Lock()

View File

@ -885,7 +885,7 @@
<div class="ui warning message">
{{ctx.Locale.Tr "repo.settings.convert_notices_1"}}
</div>
<form class="ui form" action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert">
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_confirm"))}}
@ -902,7 +902,7 @@
<div class="ui warning message">
{{ctx.Locale.Tr "repo.settings.convert_fork_notices_1"}}
</div>
<form class="ui form" action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="convert_fork">
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_fork_confirm"))}}
@ -921,7 +921,7 @@
{{ctx.Locale.Tr "repo.settings.transfer_notices_3"}} <br>
{{ctx.Locale.Tr "repo.settings.transfer_notices_4"}}
</div>
<form class="ui form" action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="transfer">
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
<div class="required field">
@ -946,7 +946,7 @@
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}
{{end}}
</div>
<form class="ui form" action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete">
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_delete"))}}
@ -1012,7 +1012,7 @@
{{ctx.Locale.Tr "repo.settings.delete_notices_1"}}<br>
{{ctx.Locale.Tr "repo.settings.wiki_delete_notices_1" .Repository.Name}}
</div>
<form class="ui form" action="{{.Link}}" method="post">
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
<input type="hidden" name="action" value="delete-wiki">
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_wiki_delete"))}}

View File

@ -0,0 +1,9 @@
import {test, expect} from '@playwright/test';
import {login} from './utils.ts';
test('heatmap tooltip shows on hover', async ({page}) => {
await login(page);
await page.goto('/');
await page.locator('.heatmap-day').first().hover();
await expect(page.locator('.tippy-box[data-state="visible"]')).toBeVisible();
});

View File

@ -372,7 +372,8 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
"action": "convert_fork",
},
)
session.MakeRequest(t, req, http.StatusSeeOther)
resp = session.MakeRequest(t, req, http.StatusOK)
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
})
// Fork repository again, and check the existence of the forked repo with unique name

View File

@ -189,7 +189,8 @@ func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoNam
req := NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
"repo_name": repoName,
})
session.MakeRequest(t, req, http.StatusSeeOther)
resp := session.MakeRequest(t, req, http.StatusOK)
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
}
func TestPullBranchDelete(t *testing.T) {

View File

@ -39,7 +39,7 @@ func TestRepositoryVisibilityChange(t *testing.T) {
"confirm_repo_name": "user2/repo1",
})
resp = session.MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
assert.True(t, repo1.IsPrivate)
@ -51,7 +51,7 @@ func TestRepositoryVisibilityChange(t *testing.T) {
"private": "false",
})
resp := session.MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, repo2.IsPrivate)

View File

@ -32,26 +32,29 @@
fill: currentcolor !important;
}
/* root legend */
#user-heatmap .vch__container > .vch__legend {
#user-heatmap .heatmap-footer {
display: flex;
font-size: 11px;
justify-content: space-between;
}
/* for the "Less" and "More" legend */
#user-heatmap .vch__legend .vch__legend {
/* "Less [colors] More" scale */
#user-heatmap .heatmap-legend {
display: flex;
align-items: center;
justify-content: right;
}
#user-heatmap .vch__legend .vch__legend div:first-child,
#user-heatmap .vch__legend .vch__legend div:last-child {
#user-heatmap .heatmap-legend-svg {
margin-right: -12px;
}
#user-heatmap .heatmap-legend > div:first-child,
#user-heatmap .heatmap-legend > div:last-child {
display: inline-block;
padding: 0 5px;
}
#user-heatmap .vch__day__square:hover {
#user-heatmap .heatmap-day:hover {
outline: 1.5px solid var(--color-text);
}

View File

@ -1,21 +1,23 @@
<script lang="ts" setup>
// TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged
import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap';
import {onMounted, shallowRef} from 'vue';
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
import {computed, onBeforeUnmount, onMounted} from 'vue';
import tippy, {createSingleton} from 'tippy.js';
import type {CreateSingletonInstance, Instance} from 'tippy.js';
defineProps<{
type HeatmapValue = {date: Date; count: number};
type HeatmapCell = {date: Date; colorIndex: number; ariaLabel: string; tooltip: string};
type MonthLabel = {monthIdx: number; weekIdx: number};
const props = defineProps<{
values: HeatmapValue[];
locale: {
textTotalContributions: string;
heatMapLocale: Partial<HeatmapLocale>;
heatMapLocale: {months: string[]; days: string[]; on: string; more: string; less: string};
noDataText: string;
tooltipUnit: string;
};
}>();
const colorRange = [
'var(--color-secondary-alpha-60)',
'var(--color-secondary-alpha-60)',
'var(--color-primary-light-4)',
'var(--color-primary-light-2)',
@ -24,21 +26,112 @@ const colorRange = [
'var(--color-primary-dark-4)',
];
const endDate = shallowRef(new Date());
const squareSize = 10;
const squareBorder = 2;
const cellSize = squareSize + squareBorder;
const daysInWeek = 7;
const trailingDays = 365;
const gridLeft = Math.ceil(squareSize * 2.5);
const gridTop = squareSize + squareSize / 2;
onMounted(() => {
// work around issue with first legend color being rendered twice and legend cut off
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper')!;
legend.setAttribute('viewBox', '12 0 80 10');
legend.style.marginRight = '-12px';
const now = new Date();
function dateKey(d: Date): string {
return `${d.getFullYear()}${String(d.getMonth()).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
}
function shiftDate(d: Date, days: number): Date {
const out = new Date(d);
out.setDate(out.getDate() + days);
return out;
}
const grid = computed(() => {
const start = shiftDate(now, -trailingDays);
const padStart = start.getDay();
const padEnd = daysInWeek - 1 - now.getDay();
const weekCount = (trailingDays + 1 + padStart + padEnd) / daysInWeek;
const maxCount = props.values.length ? Math.max(...props.values.map((v) => v.count)) : 0;
const max = maxCount > 0 ? Math.ceil(maxCount / 5 * 4) : 1;
const activities = new Map<string, {count: number; colorIndex: number}>();
for (const {date, count} of props.values) {
const colorIndex = count >= max ? 4 : Math.max(1, Math.ceil((count / max) * 3));
activities.set(dateKey(date), {count, colorIndex});
}
const {months, on} = props.locale.heatMapLocale;
const {noDataText, tooltipUnit} = props.locale;
const cursorStart = shiftDate(start, -padStart);
const cursor = new Date(cursorStart.getFullYear(), cursorStart.getMonth(), cursorStart.getDate());
const calendar: HeatmapCell[][] = [];
for (let w = 0; w < weekCount; w++) {
const week: HeatmapCell[] = [];
for (let d = 0; d < daysInWeek; d++) {
const hit = activities.get(dateKey(cursor));
const dateStr = `${months[cursor.getMonth()]} ${cursor.getDate()}, ${cursor.getFullYear()}`;
const head = hit ? `${hit.count} ${tooltipUnit}` : noDataText;
week.push({
date: new Date(cursor),
colorIndex: hit ? hit.colorIndex : 0,
ariaLabel: `${head} ${on} ${dateStr}`,
tooltip: `<b>${head}</b> ${on} ${dateStr}`,
});
cursor.setDate(cursor.getDate() + 1);
}
calendar.push(week);
}
const monthLabels: MonthLabel[] = [];
for (let w = 1; w < calendar.length; w++) {
const prev = calendar[w - 1][0].date;
const curr = calendar[w][0].date;
if (prev.getMonth() !== curr.getMonth()) {
monthLabels.push({monthIdx: curr.getMonth(), weekIdx: w});
}
}
const width = gridLeft + (cellSize * weekCount) + squareBorder;
const height = gridTop + (cellSize * daysInWeek);
return {calendar, monthLabels, width, height};
});
function handleDayClick(e: Event & {date: Date}) {
// Reset filter if same date is clicked
const legendViewBox = `${cellSize} 0 ${squareSize * (colorRange.length + 2)} ${squareSize}`;
const cellInstances = new Map<Element, Instance>();
let singleton: CreateSingletonInstance | null = null;
onMounted(() => {
singleton = createSingleton([], {
overrides: [],
moveTransition: 'transform 0.1s ease-out',
allowHTML: true,
theme: 'tooltip',
role: 'tooltip',
placement: 'top',
});
});
onBeforeUnmount(() => {
singleton?.destroy();
for (const instance of cellInstances.values()) instance.destroy();
cellInstances.clear();
});
function lazyInitTooltip(e: MouseEvent) {
const el = e.target as Element;
if (!singleton || cellInstances.has(el) || !el.classList.contains('heatmap-day')) return;
cellInstances.set(el, tippy(el, {content: el.getAttribute('data-tooltip')!}));
singleton.setInstances([...cellInstances.values()]);
}
function handleDayClick(date: Date) {
const params = new URLSearchParams(document.location.search);
const queryDate = params.get('date');
// Timezone has to be stripped because toISOString() converts to UTC
const clickedDate = new Date(e.date.getTime() - (e.date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
const clickedDate = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
if (queryDate && queryDate === clickedDate) {
params.delete('date');
@ -53,16 +146,63 @@ function handleDayClick(e: Event & {date: Date}) {
}
</script>
<template>
<calendar-heatmap
:locale="locale.heatMapLocale"
:no-data-text="locale.noDataText"
:tooltip-unit="locale.tooltipUnit"
:end-date="endDate"
:values="values"
:range-color="colorRange"
@day-click="handleDayClick($event)"
:tippy-props="{theme: 'tooltip'}"
>
<template #vch__legend-left>{{ locale.textTotalContributions }}</template>
</calendar-heatmap>
<div>
<svg class="heatmap-svg" :viewBox="`0 0 ${grid.width} ${grid.height}`">
<g class="heatmap-month-labels" :transform="`translate(${gridLeft}, 0)`">
<text
v-for="m in grid.monthLabels"
:key="m.weekIdx"
class="heatmap-month-label"
:x="cellSize * m.weekIdx"
:y="cellSize - squareBorder"
>
{{ locale.heatMapLocale.months[m.monthIdx] }}
</text>
</g>
<g class="heatmap-day-labels" :transform="`translate(0, ${gridTop})`">
<text class="heatmap-day-label" :x="0" :y="20">{{ locale.heatMapLocale.days[1] }}</text>
<text class="heatmap-day-label" :x="0" :y="44">{{ locale.heatMapLocale.days[3] }}</text>
<text class="heatmap-day-label" :x="0" :y="69">{{ locale.heatMapLocale.days[5] }}</text>
</g>
<g class="heatmap-grid" :transform="`translate(${gridLeft}, ${gridTop})`" @mouseover="lazyInitTooltip">
<g
v-for="(week, w) in grid.calendar"
:key="w"
class="heatmap-week"
:transform="`translate(${w * cellSize}, 0)`"
>
<template v-for="(day, d) in week" :key="d">
<rect
v-if="day.date < now"
class="heatmap-day"
:transform="`translate(0, ${d * cellSize})`"
:width="squareSize"
:height="squareSize"
:style="{fill: colorRange[day.colorIndex]}"
:aria-label="day.ariaLabel"
:data-tooltip="day.tooltip"
@click="handleDayClick(day.date)"
/>
</template>
</g>
</g>
</svg>
<div class="heatmap-footer">
<div>{{ locale.textTotalContributions }}</div>
<div class="heatmap-legend">
<div>{{ locale.heatMapLocale.less }}</div>
<svg class="heatmap-legend-svg" :viewBox="legendViewBox" :height="squareSize">
<rect
v-for="(color, i) in colorRange"
:key="i"
:width="squareSize"
:height="squareSize"
:x="(i + 1) * cellSize"
:style="{fill: color}"
/>
</svg>
<div>{{ locale.heatMapLocale.more }}</div>
</div>
</div>
</div>
</template>

View File

@ -29,7 +29,7 @@ test('ConfigFormValueMapper', () => {
mapper.fillFromSystemConfig();
const formData = mapper.collectToFormData();
const result: Record<string, string> = {};
const keys = [], values = [];
const keys: string[] = [], values: string[] = [];
for (const [key, value] of formData.entries()) {
if (key === 'key') keys.push(value as string);
if (key === 'value') values.push(value as string);

View File

@ -91,7 +91,7 @@ async function handleFetchActionSuccess(el: HTMLElement, opt: FetchActionOpts, r
async function handleFetchActionError(resp: Response) {
const isRespJson = resp.headers.get('content-type')?.includes('application/json');
const respText = await resp.text();
const respJson = isRespJson ? JSON.parse(await resp.text()) : null;
const respJson = isRespJson ? JSON.parse(respText) : null;
if (respJson?.errorMessage) {
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.

View File

@ -50,9 +50,9 @@ export async function attachTribute(element: HTMLElement) {
const tribute = new Tribute({
collection: [
emojiCollection as TributeCollection<any>,
mentionCollection as TributeCollection<any>,
],
emojiCollection,
mentionCollection,
] as TributeCollection<any>[],
noMatchTemplate: () => '',
});
tribute.attach(element);

View File

@ -324,7 +324,7 @@ export function hideScopedEmptyDividers(container: Element) {
handleScopeSwitch(itemScope);
}
if (!isHidden(item)) {
curScopeVisibleItems.push(item as HTMLElement);
curScopeVisibleItems.push(item);
}
}
handleScopeSwitch('');

View File

@ -432,7 +432,7 @@ class RelativeTime extends HTMLElement {
const value = d[`${unit}s` as keyof Duration] as number;
if (value || (duration.blank && unit === 'second')) {
try {
parts.push(new Intl.NumberFormat(locale, {style: 'unit', unit, unitDisplay: style} as Intl.NumberFormatOptions).format(value));
parts.push(new Intl.NumberFormat(locale, {style: 'unit', unit, unitDisplay: style}).format(value));
} catch { // PaleMoon lacks Intl.NumberFormat unit style support
parts.push(`${value} ${value === 1 ? unit : `${unit}s`}`);
}