Compare commits

...

11 Commits

Author SHA1 Message Date
silverwind
50bd7d0b24
Remove the service worker (#25010)
It's been disabled by default since 1.17
(https://github.com/go-gitea/gitea/pull/18914), and it never really
delivered any benefit except being another cache layer that has its own
unsolved invalidation issues. HTTP cache works, we don't need two cache
layers at the browser for assets.

## ⚠️ BREAKING

You can remove the config `[ui].USE_SERVICE_WORKER` from your `app.ini`
now.
2023-05-31 02:07:04 +00:00
HesterG
28a89e360f
Add user level action runners (#24995)
Used similar logic to organization.

<img width="1437" alt="Screen Shot 2023-05-30 at 10 18 06"
src="https://github.com/go-gitea/gitea/assets/17645053/49f3800a-44ae-4188-b1e6-91d49e3d7868">

<img width="1331" alt="Screen Shot 2023-05-30 at 10 31 18"
src="https://github.com/go-gitea/gitea/assets/17645053/221b2068-e9b9-4e34-bb4a-d390594b2f35">
2023-05-31 09:39:54 +08:00
6543
4c81dae297
Update github.com/google/go-github to v52 (#24004)
based on https://github.com/google/go-github/pull/2743

because of
https://github.com/go-gitea/gitea/pull/23946#discussion_r1160317554

---------

Co-authored-by: silverwind <me@silverwind.io>
2023-05-31 00:31:51 +00:00
Denys Konovalov
0c79a655d4
various style fixes (#25008)
- fixing various style issues (border color/radius, margin)
- added indent at some radio input blocks
---
### Before:
![Bildschirmfoto vom 2023-05-30
17-14-15](https://github.com/go-gitea/gitea/assets/47871822/59963646-d34f-4032-bd02-dbb48747b96d)

![Bildschirmfoto vom 2023-05-30
17-13-21](https://github.com/go-gitea/gitea/assets/47871822/87b67a35-8fbf-47ad-9903-499403dcebb5)

![Bildschirmfoto vom 2023-05-30
17-12-54](https://github.com/go-gitea/gitea/assets/47871822/6c92b427-ee54-42b1-b206-400e33d705ed)

![Bildschirmfoto vom 2023-05-30
17-05-29](https://github.com/go-gitea/gitea/assets/47871822/bd8d201d-bbc3-4c71-84fb-fb393d3c36bb)

![Bildschirmfoto vom 2023-05-30
17-04-36](https://github.com/go-gitea/gitea/assets/47871822/5f7a4e2a-f140-4162-8f3e-294356b42dee)

![Bildschirmfoto vom 2023-05-30
17-06-05](https://github.com/go-gitea/gitea/assets/47871822/d09e46b5-5e8f-41f1-af4b-24bc8cc12c21)

![Bildschirmfoto vom 2023-05-30
17-09-35](https://github.com/go-gitea/gitea/assets/47871822/179dd3f5-914d-4593-8698-46fdffd02d54)

### After:

![Bildschirmfoto vom 2023-05-30
17-14-03](https://github.com/go-gitea/gitea/assets/47871822/a404c53e-cd25-41bb-aece-bedf90c2e8ed)

![Bildschirmfoto vom 2023-05-30
17-13-35](https://github.com/go-gitea/gitea/assets/47871822/2ea8779c-9d13-44e7-8ad6-ebac10fac465)

![Bildschirmfoto vom 2023-05-30
17-12-45](https://github.com/go-gitea/gitea/assets/47871822/b34ffeb9-a7e8-4900-97ee-1894f91e189c)

![Bildschirmfoto vom 2023-05-30
17-05-18](https://github.com/go-gitea/gitea/assets/47871822/f8d18360-db3e-472a-9e86-575e6dcb4f36)

![Bildschirmfoto vom 2023-05-30
17-04-49](https://github.com/go-gitea/gitea/assets/47871822/12fc20d6-4fa5-4ac4-b788-a1506647ef47)

![Bildschirmfoto vom 2023-05-30
17-06-23](https://github.com/go-gitea/gitea/assets/47871822/2a82f8e9-3455-4619-9a2e-352c40c4e0b6)

![Bildschirmfoto vom 2023-05-30
17-11-26](https://github.com/go-gitea/gitea/assets/47871822/ab408878-45fb-4713-84c4-5777705a98ab)

---------

Co-authored-by: silverwind <me@silverwind.io>
2023-05-30 22:28:25 +00:00
HesterG
1ea5c8b0ff
Add show timestamp/seconds and fullscreen options to action page (#24876)
Part of #24728

- The timestamp shows local time and is parsed by `date.toLocaleString`;
- "show seconds" and "show timestamps" are mutually exclusive, and they
can be both hidden.


https://github.com/go-gitea/gitea/assets/17645053/89531e54-37b7-4400-a6a0-bb3cc69eb6f5

Update for timestamp format:

<img width="306" alt="Screen Shot 2023-05-25 at 09 07 47"
src="https://github.com/go-gitea/gitea/assets/17645053/2d99768d-d39c-4c9e-81a2-7bc7470399dd">

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-05-30 20:38:55 +00:00
John Olheiser
3dd3b1b456
Fix markdown link to awesome gitea (#25009)
Fixes the markdown link and uses title case like the other
translations.
2023-05-30 13:10:51 -05:00
JakobDev
1b115296d3
Followup to pinned Issues (#24945)
This addressees some things from #24406 that came up after the PR was
merged. Mostly from @delvh.

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
2023-05-30 15:26:51 +00:00
Lunny Xiao
faae819f5d
revert the removed method to fix tmpl break on graph page (#25005)
Fix #24996 
Caused by #24634
2023-05-30 21:36:58 +08:00
wxiaoguang
ee99cf6313
Refactor diffFileInfo / DiffTreeStore (#24998)
Follow  #21012, #22399

Replace #24983, fix #24938

Help #24956

Now, the `window.config.pageData.diffFileInfo` itself is a reactive
store, so it's quite easy to sync values/states by it, no need to do
"doLoadMoreFiles" or "callback".

Screenshot: these two buttons both work. After complete loading, the UI
is also right.

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/cc6310fd-7f27-45ea-ab4f-24952a87b421)


![image](https://github.com/go-gitea/gitea/assets/2114189/4c11dd67-ac03-4568-8541-91204d27a4e3)


![image](https://github.com/go-gitea/gitea/assets/2114189/38a22cec-41be-41e6-a209-f347b7a4c1de)

</details>
2023-05-30 18:53:15 +08:00
HesterG
32185efc14
Fix delete user account modal (#25004)
Before:

<img width="953" alt="Screen Shot 2023-05-30 at 17 08 57"
src="https://github.com/go-gitea/gitea/assets/17645053/f379b2c9-7d9a-492e-b0e4-5a8be1c3a025">

After:

<img width="875" alt="Screen Shot 2023-05-30 at 17 06 17"
src="https://github.com/go-gitea/gitea/assets/17645053/75d3c9b5-201b-4001-a27e-64c932e2e34a">
2023-05-30 18:08:09 +08:00
silverwind
d783384c19
Clean up github actions (#24984)
- Merge the file filters into `files-changed.yml`
- Remove unused yaml anchors like `&backend`
- Merge the `compliance-docs` workflow into `compliance`
- Add actions linting
- Misc cleanups for whitespace and step names
2023-05-30 13:31:00 +08:00
57 changed files with 481 additions and 384 deletions

View File

@ -76,7 +76,6 @@ cpu.out
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/public/js /public/js
/public/serviceworker.js
/public/css /public/css
/public/fonts /public/fonts
/public/img/webpack /public/img/webpack

View File

@ -1,15 +0,0 @@
docs: &docs
- "**/*.md"
- "docs/**"
backend: &backend
- "**/*.go"
- "**/*.tmpl"
- "go.mod"
- "go.sum"
frontend: &frontend
- "**/*.js"
- "web_src/**"
- "package.json"
- "package-lock.json"

View File

@ -1,32 +1,53 @@
name: files changed name: files-changed
on: on:
workflow_call: workflow_call:
outputs: outputs:
docs:
description: "whether docs files changed"
value: ${{ jobs.files-changed.outputs.docs }}
backend: backend:
description: "whether backend files changed" description: "whether backend files changed"
value: ${{ jobs.files-changed.outputs.backend }} value: ${{ jobs.detect.outputs.backend }}
frontend: frontend:
description: "whether frontend files changed" description: "whether frontend files changed"
value: ${{ jobs.files-changed.outputs.frontend }} value: ${{ jobs.detect.outputs.frontend }}
docs:
description: "whether docs files changed"
value: ${{ jobs.detect.outputs.docs }}
actions:
description: "whether actions files changed"
value: ${{ jobs.detect.outputs.actions }}
jobs: jobs:
files-changed: detect:
name: detect which files changed name: detect which files changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 3 timeout-minutes: 3
# Map a step output to a job output # Map a step output to a job output
outputs: outputs:
docs: ${{ steps.changes.outputs.docs }}
backend: ${{ steps.changes.outputs.backend }} backend: ${{ steps.changes.outputs.backend }}
frontend: ${{ steps.changes.outputs.frontend }} frontend: ${{ steps.changes.outputs.frontend }}
docs: ${{ steps.changes.outputs.docs }}
actions: ${{ steps.changes.outputs.actions }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Check for backend file changes - uses: dorny/paths-filter@v2
uses: dorny/paths-filter@v2
id: changes id: changes
with: with:
filters: .github/file-filters.yml filters: |
backend:
- "**/*.go"
- "**/*.tmpl"
- "go.mod"
- "go.sum"
frontend:
- "**/*.js"
- "web_src/**"
- "package.json"
- "package-lock.json"
docs:
- "**/*.md"
- "docs/**"
actions:
- ".github/workflows/*"

View File

@ -1,25 +0,0 @@
name: compliance-docs
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
compliance-docs:
if: needs.files-changed.outputs.docs == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: make deps-frontend
- run: make lint-md
- run: make docs # test if build could succeed

View File

@ -25,6 +25,7 @@ jobs:
- run: make lint-backend - run: make lint-backend
env: env:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
lint-go-windows: lint-go-windows:
if: needs.files-changed.outputs.backend == 'true' if: needs.files-changed.outputs.backend == 'true'
needs: files-changed needs: files-changed
@ -41,6 +42,7 @@ jobs:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
GOOS: windows GOOS: windows
GOARCH: amd64 GOARCH: amd64
lint-go-gogit: lint-go-gogit:
if: needs.files-changed.outputs.backend == 'true' if: needs.files-changed.outputs.backend == 'true'
needs: files-changed needs: files-changed
@ -55,6 +57,7 @@ jobs:
- run: make lint-go - run: make lint-go
env: env:
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit sqlite sqlite_unlock_notify
checks-backend: checks-backend:
if: needs.files-changed.outputs.backend == 'true' if: needs.files-changed.outputs.backend == 'true'
needs: files-changed needs: files-changed
@ -67,6 +70,7 @@ jobs:
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs - run: make --always-make checks-backend # ensure the "go-licenses" make target runs
frontend: frontend:
if: needs.files-changed.outputs.frontend == 'true' if: needs.files-changed.outputs.frontend == 'true'
needs: files-changed needs: files-changed
@ -79,6 +83,7 @@ jobs:
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
backend: backend:
if: needs.files-changed.outputs.backend == 'true' if: needs.files-changed.outputs.backend == 'true'
needs: files-changed needs: files-changed
@ -113,3 +118,25 @@ jobs:
env: env:
GOOS: linux GOOS: linux
GOARCH: 386 GOARCH: 386
docs:
if: needs.files-changed.outputs.docs == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: make deps-frontend
- run: make lint-md
- run: make docs # test if build could succeed
actions:
if: needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
- run: make lint-actions

1
.gitignore vendored
View File

@ -77,7 +77,6 @@ cpu.out
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/public/js /public/js
/public/serviceworker.js
/public/css /public/css
/public/fonts /public/fonts
/public/img/webpack /public/img/webpack

View File

@ -1,5 +1,6 @@
*.min.css *.min.css
*.min.js *.min.js
/assets/*.json
/modules/options/bindata.go /modules/options/bindata.go
/modules/public/bindata.go /modules/public/bindata.go
/modules/templates/bindata.go /modules/templates/bindata.go

View File

@ -114,7 +114,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.js WEBPACK_CONFIGS := webpack.config.js
WEBPACK_DEST := public/js/index.js public/css/index.css WEBPACK_DEST := public/js/index.js public/css/index.css
WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack public/serviceworker.js WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST)) BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))

View File

@ -525,8 +525,8 @@
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" "licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
}, },
{ {
"name": "github.com/google/go-github/v51/github", "name": "github.com/google/go-github/v52/github",
"path": "github.com/google/go-github/v51/github/LICENSE", "path": "github.com/google/go-github/v52/github/LICENSE",
"licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" "licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
}, },
{ {

View File

@ -17,7 +17,7 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/google/go-github/v51/github" "github.com/google/go-github/v52/github"
"github.com/urfave/cli" "github.com/urfave/cli"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View File

@ -1044,7 +1044,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; List of reasons why a Pull Request or Issue can be locked ;; List of reasons why a Pull Request or Issue can be locked
;LOCK_REASONS = Too heated,Off-topic,Resolved,Spam ;LOCK_REASONS = Too heated,Off-topic,Resolved,Spam
;; Maximum number of pinned Issues ;; Maximum number of pinned Issues per repo
;; Set to 0 to disable pinning Issues ;; Set to 0 to disable pinning Issues
;MAX_PINNED = 3 ;MAX_PINNED = 3
@ -1208,9 +1208,6 @@ LEVEL = Info
;; Whether to search within description at repository search on explore page. ;; Whether to search within description at repository search on explore page.
;SEARCH_REPO_DESCRIPTION = true ;SEARCH_REPO_DESCRIPTION = true
;; ;;
;; Whether to enable a Service Worker to cache frontend assets
;USE_SERVICE_WORKER = false
;;
;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. ;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). ;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
;ONLY_SHOW_RELEVANT_REPOS = false ;ONLY_SHOW_RELEVANT_REPOS = false

View File

@ -141,7 +141,7 @@ In addition there is _`StaticRootPath`_ which can be set as a built-in at build
### Repository - Issue (`repository.issue`) ### Repository - Issue (`repository.issue`)
- `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked - `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked
- `MAX_PINNED`: **3**: Maximum number of pinned Issues. Set to 0 to disable pinning Issues. - `MAX_PINNED`: **3**: Maximum number of pinned Issues per Repo. Set to 0 to disable pinning Issues.
### Repository - Upload (`repository.upload`) ### Repository - Upload (`repository.upload`)
@ -230,7 +230,6 @@ The following configuration set `Content-Type: application/vnd.android.package-a
add it to this config. add it to this config.
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. - `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page. - `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
- `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets.
- `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. - `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).

View File

@ -301,4 +301,4 @@ You can try it out using [the online demo](https://try.gitea.io/).
## Integrated support ## Integrated support
Please visit [AWESOME GITEA] (https://gitea.com/gitea/awesome-gitea/) to get more third-party integrated support Please visit [Awesome Gitea](https://gitea.com/gitea/awesome-gitea/) to get more third-party integrated support

8
go.mod
View File

@ -55,7 +55,7 @@ require (
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/go-github/v51 v51.0.0 github.com/google/go-github/v52 v52.0.0
github.com/google/pprof v0.0.0-20230502171905-255e3b9b56de github.com/google/pprof v0.0.0-20230502171905-255e3b9b56de
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
@ -107,10 +107,10 @@ require (
github.com/yuin/goldmark v1.5.4 github.com/yuin/goldmark v1.5.4
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.8.0 golang.org/x/crypto v0.9.0
golang.org/x/image v0.7.0 golang.org/x/image v0.7.0
golang.org/x/net v0.10.0 golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.7.0 golang.org/x/oauth2 v0.8.0
golang.org/x/sys v0.8.0 golang.org/x/sys v0.8.0
golang.org/x/text v0.9.0 golang.org/x/text v0.9.0
golang.org/x/tools v0.8.0 golang.org/x/tools v0.8.0
@ -136,7 +136,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230528122434-6f98819771a1 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect

18
go.sum
View File

@ -105,8 +105,8 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 h1:L6S7kR7SlhQKplIBpkra3s6yhcZV51lhRnXmYc4HohI= github.com/ProtonMail/go-crypto v0.0.0-20230528122434-6f98819771a1 h1:JMDGhoQvXNTqH6Y3MC0IUw6tcZvaUdujNqzK2HYWZc8=
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-crypto v0.0.0-20230528122434-6f98819771a1/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -227,6 +227,7 @@ github.com/bufbuild/connect-go v1.7.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrI
github.com/buildkite/terminal-to-html/v3 v3.7.0 h1:chdLUSpiOj/A4v3dzxyOqixXI6aw7IDA6Dk77FXsvNU= github.com/buildkite/terminal-to-html/v3 v3.7.0 h1:chdLUSpiOj/A4v3dzxyOqixXI6aw7IDA6Dk77FXsvNU=
github.com/buildkite/terminal-to-html/v3 v3.7.0/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo= github.com/buildkite/terminal-to-html/v3 v3.7.0/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE= github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE= github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
@ -564,8 +565,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v51 v51.0.0 h1:KCjsbgPV28VoRftdP+K2mQL16jniUsLAJknsOVKwHyU= github.com/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M=
github.com/google/go-github/v51 v51.0.0/go.mod h1:kZj/rn/c1lSUbr/PFWl2hhusPV7a5XNYKcwPrd5L3Us= github.com/google/go-github/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
@ -1318,10 +1319,11 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1430,8 +1432,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -69,7 +69,11 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
return types.OwnerTypeRepository return types.OwnerTypeRepository
} }
if r.OwnerID != 0 { if r.OwnerID != 0 {
return types.OwnerTypeOrganization if r.Owner.Type == user_model.UserTypeOrganization {
return types.OwnerTypeOrganization
} else if r.Owner.Type == user_model.UserTypeIndividual {
return types.OwnerTypeIndividual
}
} }
return types.OwnerTypeSystemGlobal return types.OwnerTypeSystemGlobal
} }

View File

@ -687,6 +687,8 @@ func (issue *Issue) HasOriginalAuthor() bool {
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0 return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0
} }
var ErrIssueMaxPinReached = util.NewInvalidArgumentErrorf("the max number of pinned issues has been readched")
// IsPinned returns if a Issue is pinned // IsPinned returns if a Issue is pinned
func (issue *Issue) IsPinned() bool { func (issue *Issue) IsPinned() bool {
return issue.PinOrder != 0 return issue.PinOrder != 0
@ -707,7 +709,7 @@ func (issue *Issue) Pin(ctx context.Context, user *user_model.User) error {
// Check if the maximum allowed Pins reached // Check if the maximum allowed Pins reached
if maxPin >= setting.Repository.Issue.MaxPinned { if maxPin >= setting.Repository.Issue.MaxPinned {
return fmt.Errorf("You have reached the max number of pinned Issues") return ErrIssueMaxPinReached
} }
_, err = db.GetEngine(ctx).Table("issue"). _, err = db.GetEngine(ctx).Table("issue").
@ -856,10 +858,15 @@ func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) ([]*Issue,
// IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned // IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned
func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) { func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) {
var maxPin int var maxPin int
_, err := db.GetEngine(ctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", repoID, isPull).Get(&maxPin) _, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ? AND pin_order > 0", repoID, isPull).Get(&maxPin)
if err != nil { if err != nil {
return false, err return false, err
} }
return maxPin < setting.Repository.Issue.MaxPinned, nil return maxPin < setting.Repository.Issue.MaxPinned, nil
} }
// IsErrIssueMaxPinReached returns if the error is, that the User can't pin more Issues
func IsErrIssueMaxPinReached(err error) bool {
return err == ErrIssueMaxPinReached
}

View File

@ -53,6 +53,11 @@ func (ref *Reference) Commit() (*Commit, error) {
return ref.repo.getCommit(ref.Object) return ref.repo.getCommit(ref.Object)
} }
// ShortName returns the short name of the reference
func (ref *Reference) ShortName() string {
return RefName(ref.Name).ShortName()
}
// RefGroup returns the group type of the reference // RefGroup returns the group type of the reference
func (ref *Reference) RefGroup() string { func (ref *Reference) RefGroup() string {
return RefName(ref.Name).RefGroup() return RefName(ref.Name).RefGroup()

View File

@ -32,7 +32,6 @@ var UI = struct {
CustomEmojis []string CustomEmojis []string
CustomEmojisMap map[string]string `ini:"-"` CustomEmojisMap map[string]string `ini:"-"`
SearchRepoDescription bool SearchRepoDescription bool
UseServiceWorker bool
OnlyShowRelevantRepos bool OnlyShowRelevantRepos bool
Notification struct { Notification struct {
@ -136,7 +135,6 @@ func loadUIFrom(rootCfg ConfigProvider) {
UI.ShowUserEmail = sec.Key("SHOW_USER_EMAIL").MustBool(true) UI.ShowUserEmail = sec.Key("SHOW_USER_EMAIL").MustBool(true)
UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false)
UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true) UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true)
UI.UseServiceWorker = sec.Key("USE_SERVICE_WORKER").MustBool(false)
// OnlyShowRelevantRepos=false is important for many private/enterprise instances, // OnlyShowRelevantRepos=false is important for many private/enterprise instances,
// because many private repositories do not have "description/topic", users just want to search by their names. // because many private repositories do not have "description/topic", users just want to search by their names.

View File

@ -127,9 +127,6 @@ func NewFuncMap() template.FuncMap {
"MetaKeywords": func() string { "MetaKeywords": func() string {
return setting.UI.Meta.Keywords return setting.UI.Meta.Keywords
}, },
"UseServiceWorker": func() bool {
return setting.UI.UseServiceWorker
},
"EnableTimetracking": func() bool { "EnableTimetracking": func() bool {
return setting.Service.EnableTimetracking return setting.Service.EnableTimetracking
}, },

View File

@ -125,6 +125,10 @@ concept_user_individual = Individual
concept_code_repository = Repository concept_code_repository = Repository
concept_user_organization = Organization concept_user_organization = Organization
show_timestamps = Show timestamps
show_log_seconds = Show seconds
show_full_screen = Show full screen
[aria] [aria]
navbar = Navigation Bar navbar = Navigation Bar
footer = Footer footer = Footer

23
package-lock.json generated
View File

@ -51,8 +51,6 @@
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.84.1", "webpack": "5.84.1",
"webpack-cli": "5.1.1", "webpack-cli": "5.1.1",
"workbox-routing": "6.6.0",
"workbox-strategies": "6.6.0",
"wrap-ansi": "8.1.0" "wrap-ansi": "8.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -11045,27 +11043,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/workbox-core": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz",
"integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ=="
},
"node_modules/workbox-routing": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz",
"integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==",
"dependencies": {
"workbox-core": "6.6.0"
}
},
"node_modules/workbox-strategies": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz",
"integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==",
"dependencies": {
"workbox-core": "6.6.0"
}
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",

View File

@ -51,8 +51,6 @@
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.84.1", "webpack": "5.84.1",
"webpack-cli": "5.1.1", "webpack-cli": "5.1.1",
"workbox-routing": "6.6.0",
"workbox-strategies": "6.6.0",
"wrap-ansi": "8.1.0" "wrap-ansi": "8.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -45,6 +45,8 @@ func PinIssue(ctx *context.APIContext) {
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else if issues_model.IsErrIssueMaxPinReached(err) {
ctx.Error(http.StatusBadRequest, "MaxPinReached", err)
} else { } else {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err) ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
} }
@ -55,11 +57,13 @@ func PinIssue(ctx *context.APIContext) {
err = issue.LoadRepo(ctx) err = issue.LoadRepo(ctx)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadRepo", err) ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
return
} }
err = issue.Pin(ctx, ctx.Doer) err = issue.Pin(ctx, ctx.Doer)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "PinIssue", err) ctx.Error(http.StatusInternalServerError, "PinIssue", err)
return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
@ -108,11 +112,13 @@ func UnpinIssue(ctx *context.APIContext) {
err = issue.LoadRepo(ctx) err = issue.LoadRepo(ctx)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadRepo", err) ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
return
} }
err = issue.Unpin(ctx, ctx.Doer) err = issue.Unpin(ctx, ctx.Doer)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "UnpinIssue", err) ctx.Error(http.StatusInternalServerError, "UnpinIssue", err)
return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
@ -166,6 +172,7 @@ func MoveIssuePin(ctx *context.APIContext) {
err = issue.MovePin(ctx, int(ctx.ParamsInt64(":position"))) err = issue.MovePin(ctx, int(ctx.ParamsInt64(":position")))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "MovePin", err) ctx.Error(http.StatusInternalServerError, "MovePin", err)
return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
@ -193,12 +200,12 @@ func ListPinnedIssues(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"
issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, false) issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, false)
if err != nil {
if err == nil {
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues))
} else {
ctx.Error(http.StatusInternalServerError, "LoadPinnedIssues", err) ctx.Error(http.StatusInternalServerError, "LoadPinnedIssues", err)
return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues))
} }
// ListPinnedPullRequests returns a list of all pinned PRs // ListPinnedPullRequests returns a list of all pinned PRs
@ -225,6 +232,7 @@ func ListPinnedPullRequests(ctx *context.APIContext) {
issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, true) issues, err := issues_model.GetPinnedIssues(ctx, ctx.Repo.Repository.ID, true)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadPinnedPullRequests", err) ctx.Error(http.StatusInternalServerError, "LoadPinnedPullRequests", err)
return
} }
apiPrs := make([]*api.PullRequest, len(issues)) apiPrs := make([]*api.PullRequest, len(issues))

View File

@ -9,6 +9,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
) )
// IssuePinOrUnpin pin or unpin a Issue // IssuePinOrUnpin pin or unpin a Issue
@ -19,12 +20,14 @@ func IssuePinOrUnpin(ctx *context.Context) {
err := issue.LoadRepo(ctx) err := issue.LoadRepo(ctx)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
err = issue.PinOrUnpin(ctx, ctx.Doer) err = issue.PinOrUnpin(ctx, ctx.Doer)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
@ -33,9 +36,10 @@ func IssuePinOrUnpin(ctx *context.Context) {
// IssueUnpin unpins a Issue // IssueUnpin unpins a Issue
func IssueUnpin(ctx *context.Context) { func IssueUnpin(ctx *context.Context) {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
@ -43,12 +47,15 @@ func IssueUnpin(ctx *context.Context) {
err = issue.LoadRepo(ctx) err = issue.LoadRepo(ctx)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
err = issue.Unpin(ctx, ctx.Doer) err = issue.Unpin(ctx, ctx.Doer)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
@ -69,18 +76,21 @@ func IssuePinMove(ctx *context.Context) {
form := &movePinIssueForm{} form := &movePinIssueForm{}
if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
issue, err := issues_model.GetIssueByID(ctx, form.ID) issue, err := issues_model.GetIssueByID(ctx, form.ID)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }
err = issue.MovePin(ctx, form.Position) err = issue.MovePin(ctx, form.Position)
if err != nil { if err != nil {
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
return return
} }

View File

@ -21,9 +21,11 @@ const (
tplRepoRunners base.TplName = "repo/settings/actions" tplRepoRunners base.TplName = "repo/settings/actions"
tplOrgRunners base.TplName = "org/settings/actions" tplOrgRunners base.TplName = "org/settings/actions"
tplAdminRunners base.TplName = "admin/actions" tplAdminRunners base.TplName = "admin/actions"
tplUserRunners base.TplName = "user/settings/actions"
tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit" tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit"
tplOrgRunnerEdit base.TplName = "org/settings/runners_edit" tplOrgRunnerEdit base.TplName = "org/settings/runners_edit"
tplAdminRunnerEdit base.TplName = "admin/runners/edit" tplAdminRunnerEdit base.TplName = "admin/runners/edit"
tplUserRunnerEdit base.TplName = "user/settings/runner_edit"
) )
type runnersCtx struct { type runnersCtx struct {
@ -32,6 +34,7 @@ type runnersCtx struct {
IsRepo bool IsRepo bool
IsOrg bool IsOrg bool
IsAdmin bool IsAdmin bool
IsUser bool
RunnersTemplate base.TplName RunnersTemplate base.TplName
RunnerEditTemplate base.TplName RunnerEditTemplate base.TplName
RedirectLink string RedirectLink string
@ -71,6 +74,17 @@ func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) {
}, nil }, nil
} }
if ctx.Data["PageIsUserSettings"] == true {
return &runnersCtx{
OwnerID: ctx.Doer.ID,
RepoID: 0,
IsUser: true,
RunnersTemplate: tplUserRunners,
RunnerEditTemplate: tplUserRunnerEdit,
RedirectLink: setting.AppSubURL + "/user/settings/actions/runners/",
}, nil
}
return nil, errors.New("unable to set Runners context") return nil, errors.New("unable to set Runners context")
} }
@ -102,7 +116,7 @@ func Runners(ctx *context.Context) {
if rCtx.IsRepo { if rCtx.IsRepo {
opts.RepoID = rCtx.RepoID opts.RepoID = rCtx.RepoID
opts.WithAvailable = true opts.WithAvailable = true
} else if rCtx.IsOrg { } else if rCtx.IsOrg || rCtx.IsUser {
opts.OwnerID = rCtx.OwnerID opts.OwnerID = rCtx.OwnerID
opts.WithAvailable = true opts.WithAvailable = true
} }

View File

@ -9,5 +9,5 @@ import (
) )
func RedirectToDefaultSetting(ctx *context.Context) { func RedirectToDefaultSetting(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/actions/secrets") ctx.Redirect(setting.AppSubURL + "/user/settings/actions/runners")
} }

View File

@ -492,6 +492,7 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() { m.Group("/actions", func() {
m.Get("", user_setting.RedirectToDefaultSetting) m.Get("", user_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes() addSettingsSecretsRoutes()
}, actions.MustEnableActions) }, actions.MustEnableActions)
@ -1025,8 +1026,8 @@ func registerRoutes(m *web.Route) {
m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation) m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
m.Post("/attachments", repo.UploadIssueAttachment) m.Post("/attachments", repo.UploadIssueAttachment)
m.Post("/attachments/remove", repo.DeleteAttachment) m.Post("/attachments/remove", repo.DeleteAttachment)
m.Delete("/unpin/{id}", reqRepoAdmin, repo.IssueUnpin) m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
m.Post("/pin_move", reqRepoAdmin, repo.IssuePinMove) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove)
}, context.RepoMustNotBeArchived()) }, context.RepoMustNotBeArchived())
m.Group("/comments/{id}", func() { m.Group("/comments/{id}", func() {
m.Post("", repo.UpdateCommentContent) m.Post("", repo.UpdateCommentContent)

View File

@ -7,7 +7,7 @@ package migrations
import ( import (
"errors" "errors"
"github.com/google/go-github/v51/github" "github.com/google/go-github/v52/github"
) )
// ErrRepoNotCreated returns the error that repository not created // ErrRepoNotCreated returns the error that repository not created

View File

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"github.com/google/go-github/v51/github" "github.com/google/go-github/v52/github"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -256,11 +256,11 @@ func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) {
milestones = append(milestones, &base.Milestone{ milestones = append(milestones, &base.Milestone{
Title: m.GetTitle(), Title: m.GetTitle(),
Description: m.GetDescription(), Description: m.GetDescription(),
Deadline: convertGithubTimestampToTime(m.DueOn), Deadline: m.DueOn.GetTime(),
State: state, State: state,
Created: m.GetCreatedAt().Time, Created: m.GetCreatedAt().Time,
Updated: convertGithubTimestampToTime(m.UpdatedAt), Updated: m.UpdatedAt.GetTime(),
Closed: convertGithubTimestampToTime(m.ClosedAt), Closed: m.ClosedAt.GetTime(),
}) })
} }
if len(ms) < perPage { if len(ms) < perPage {
@ -715,11 +715,11 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
State: pr.GetState(), State: pr.GetState(),
Created: pr.GetCreatedAt().Time, Created: pr.GetCreatedAt().Time,
Updated: pr.GetUpdatedAt().Time, Updated: pr.GetUpdatedAt().Time,
Closed: convertGithubTimestampToTime(pr.ClosedAt), Closed: pr.ClosedAt.GetTime(),
Labels: labels, Labels: labels,
Merged: pr.MergedAt != nil, Merged: pr.MergedAt != nil,
MergeCommitSHA: pr.GetMergeCommitSHA(), MergeCommitSHA: pr.GetMergeCommitSHA(),
MergedTime: convertGithubTimestampToTime(pr.MergedAt), MergedTime: pr.MergedAt.GetTime(),
IsLocked: pr.ActiveLockReason != nil, IsLocked: pr.ActiveLockReason != nil,
Head: base.PullRequestBranch{ Head: base.PullRequestBranch{
Ref: pr.GetHead().GetRef(), Ref: pr.GetHead().GetRef(),
@ -878,10 +878,3 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev
} }
return allReviews, nil return allReviews, nil
} }
func convertGithubTimestampToTime(t *github.Timestamp) *time.Time {
if t == nil {
return nil
}
return &t.Time
}

View File

@ -5,7 +5,7 @@
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/admin">
<table class="ui very basic striped table unstackable"> <table class="ui very basic striped table unstackable gt-mb-0">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>

View File

@ -19,7 +19,7 @@
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/admin">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<table class="ui very basic table gt-px-4"> <table class="ui very basic table gt-mt-0 gt-px-4">
<tbody> <tbody>
<tr> <tr>
<td>{{.locale.Tr "admin.dashboard.delete_inactive_accounts"}}</td> <td>{{.locale.Tr "admin.dashboard.delete_inactive_accounts"}}</td>

View File

@ -166,7 +166,7 @@
<label>{{.locale.Tr "settings.lookup_avatar_by_mail"}}</label> <label>{{.locale.Tr "settings.lookup_avatar_by_mail"}}</label>
</div> </div>
</div> </div>
<div class="field {{if .Err_Gravatar}}error{{end}}"> <div class="field gt-pl-4 {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{.locale.Tr "email"}}</label> <label for="gravatar">Avatar {{.locale.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.User.AvatarEmail}}"> <input id="gravatar" name="gravatar" value="{{.User.AvatarEmail}}">
</div> </div>
@ -179,7 +179,7 @@
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field gt-pl-4">
<label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label> <label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> <input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div> </div>
@ -197,17 +197,17 @@
{{svg "octicon-trash"}} {{svg "octicon-trash"}}
{{.locale.Tr "settings.delete_account_title"}} {{.locale.Tr "settings.delete_account_title"}}
</div> </div>
<div class="content">
<p>{{.locale.Tr "settings.delete_account_desc"}}</p>
</div>
<form class="ui form" method="POST" action="{{.Link}}/delete"> <form class="ui form" method="POST" action="{{.Link}}/delete">
{{$.CsrfTokenHtml}} <div class="content">
<div class="field"> <p>{{.locale.Tr "settings.delete_account_desc"}}</p>
<div class="ui checkbox"> {{$.CsrfTokenHtml}}
<label for="purge">{{.locale.Tr "admin.users.purge"}}</label> <div class="field">
<input name="purge" type="checkbox"> <div class="ui checkbox">
<label for="purge">{{.locale.Tr "admin.users.purge"}}</label>
<input name="purge" type="checkbox">
</div>
<p class="help">{{.locale.Tr "admin.users.purge_help"}}</p>
</div> </div>
<p class="help">{{.locale.Tr "admin.users.purge_help"}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</form> </form>

View File

@ -12,7 +12,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
assetUrlPrefix: '{{AssetUrlPrefix}}', assetUrlPrefix: '{{AssetUrlPrefix}}',
runModeIsProd: {{.RunModeIsProd}}, runModeIsProd: {{.RunModeIsProd}},
customEmojis: {{CustomEmojis}}, customEmojis: {{CustomEmojis}},
useServiceWorker: {{UseServiceWorker}},
csrfToken: '{{.CsrfToken}}', csrfToken: '{{.CsrfToken}}',
pageData: {{.PageData}}, pageData: {{.PageData}},
notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}} notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}

View File

@ -19,6 +19,9 @@
data-locale-status-skipped="{{.locale.Tr "actions.status.skipped"}}" data-locale-status-skipped="{{.locale.Tr "actions.status.skipped"}}"
data-locale-status-blocked="{{.locale.Tr "actions.status.blocked"}}" data-locale-status-blocked="{{.locale.Tr "actions.status.blocked"}}"
data-locale-artifacts-title="{{$.locale.Tr "artifacts"}}" data-locale-artifacts-title="{{$.locale.Tr "artifacts"}}"
data-locale-show-timestamps="{{.locale.Tr "show_timestamps"}}"
data-locale-show-log-seconds="{{.locale.Tr "show_log_seconds"}}"
data-locale-show-full-screen="{{.locale.Tr "show_full_screen"}}"
> >
</div> </div>
</div> </div>

View File

@ -45,33 +45,31 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<script id="diff-data-script"> <script id="diff-data-script" type="module">
(() => { const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}}},{{end}}];
const diffData = { const diffData = {
files: [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}}},{{end}}], isIncomplete: {{.Diff.IsIncomplete}},
isIncomplete: {{.Diff.IsIncomplete}}, tooManyFilesMessage: "{{$.locale.Tr "repo.diff.too_many_files"}}",
tooManyFilesMessage: "{{$.locale.Tr "repo.diff.too_many_files"}}", binaryFileMessage: "{{$.locale.Tr "repo.diff.bin"}}",
binaryFileMessage: "{{$.locale.Tr "repo.diff.bin"}}", showMoreMessage: "{{.locale.Tr "repo.diff.show_more"}}",
showMoreMessage: "{{.locale.Tr "repo.diff.show_more"}}", statisticsMessage: "{{.locale.Tr "repo.diff.stats_desc_file"}}",
statisticsMessage: "{{.locale.Tr "repo.diff.stats_desc_file"}}", linkLoadMore: "{{$.Link}}?skip-to={{.Diff.End}}&file-only=true",
fileTreeIsVisible: false, };
fileListIsVisible: false,
isLoadingNewData: false, // for first time loading, the diffFileInfo is a plain object
diffEnd: {{.Diff.End}}, // after the Vue component is mounted, the diffFileInfo is a reactive object
link: "{{$.Link}}" // keep in mind that this script block would be executed many times when loading more files, by "loadMoreFiles"
}; let diffFileInfo = window.config.pageData.diffFileInfo || {
if(window.config.pageData.diffFileInfo) { files:[],
// Page is already loaded - add the data to our existing data fileTreeIsVisible: false,
window.config.pageData.diffFileInfo.files.push(...diffData.files); fileListIsVisible: false,
window.config.pageData.diffFileInfo.isIncomplete = diffData.isIncomplete; isLoadingNewData: false,
window.config.pageData.diffFileInfo.diffEnd = diffData.diffEnd; selectedItem: '',
window.config.pageData.diffFileInfo.link = diffData.link; };
} else { diffFileInfo = Object.assign(diffFileInfo, diffData);
// new load of page - populate initial data diffFileInfo.files.push(...diffDataFiles);
window.config.pageData.diffFileInfo = diffData; window.config.pageData.diffFileInfo = diffFileInfo;
} </script>
})();
</script>
<div id="diff-file-list"></div> <div id="diff-file-list"></div>
<div id="diff-container"> <div id="diff-container">
<div id="diff-file-tree" class="gt-hidden"></div> <div id="diff-file-tree" class="gt-hidden"></div>

View File

@ -6,7 +6,7 @@
{{if .PinnedIssues}} {{if .PinnedIssues}}
<div id="issue-pins" {{if .IsRepoAdmin}}data-is-repo-admin{{end}}> <div id="issue-pins" {{if .IsRepoAdmin}}data-is-repo-admin{{end}}>
{{range .PinnedIssues}} {{range .PinnedIssues}}
<div class="pinned-issue-card gt-word-break" data-move-url="{{$.Link}}/pin_move" data-issue-id="{{.ID}}"> <div class="pinned-issue-card gt-word-break" data-move-url="{{$.Link}}/move_pin" data-issue-id="{{.ID}}">
{{if eq $.Project.CardType 1}} {{if eq $.Project.CardType 1}}
<div class="card-attachment-images"> <div class="card-attachment-images">
{{range (index $.issuesAttachmentMap .ID)}} {{range (index $.issuesAttachmentMap .ID)}}
@ -21,7 +21,7 @@
</div> </div>
<a class="pinned-issue-title muted issue-title" href="{{.Link}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a> <a class="pinned-issue-title muted issue-title" href="{{.Link}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{if $.IsRepoAdmin}} {{if $.IsRepoAdmin}}
<a role="button" class="pinned-issue-unpin muted gt-df gt-ac" data-tooltip-content={{$.locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Link}}/unpin/{{.ID}}"> <a role="button" class="pinned-issue-unpin muted gt-df gt-ac" data-tooltip-content={{$.locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Link}}/unpin/{{.Index}}">
{{svg "octicon-x" 16}} {{svg "octicon-x" 16}}
</a> </a>
{{end}} {{end}}

View File

@ -330,7 +330,7 @@
<label>{{.locale.Tr "repo.settings.use_external_wiki"}}</label> <label>{{.locale.Tr "repo.settings.use_external_wiki"}}</label>
</div> </div>
</div> </div>
<div class="field {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box"> <div class="field gt-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
<label for="external_wiki_url">{{.locale.Tr "repo.settings.external_wiki_url"}}</label> <label for="external_wiki_url">{{.locale.Tr "repo.settings.external_wiki_url"}}</label>
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}"> <input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
<p class="help">{{.locale.Tr "repo.settings.external_wiki_url_desc"}}</p> <p class="help">{{.locale.Tr "repo.settings.external_wiki_url_desc"}}</p>
@ -362,7 +362,7 @@
<label>{{.locale.Tr "repo.settings.use_internal_issue_tracker"}}</label> <label>{{.locale.Tr "repo.settings.use_internal_issue_tracker"}}</label>
</div> </div>
</div> </div>
<div class="field {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box"> <div class="field gt-pl-4 {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
{{if .Repository.CanEnableTimetracker}} {{if .Repository.CanEnableTimetracker}}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
@ -398,7 +398,7 @@
<label>{{.locale.Tr "repo.settings.use_external_issue_tracker"}}</label> <label>{{.locale.Tr "repo.settings.use_external_issue_tracker"}}</label>
</div> </div>
</div> </div>
<div class="field {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box"> <div class="field gt-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box">
<div class="field"> <div class="field">
<label for="external_tracker_url">{{.locale.Tr "repo.settings.external_tracker_url"}}</label> <label for="external_tracker_url">{{.locale.Tr "repo.settings.external_tracker_url"}}</label>
<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerURL}}"> <input id="external_tracker_url" name="external_tracker_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerURL}}">

View File

@ -2,6 +2,8 @@
<div class="user-setting-content"> <div class="user-setting-content">
{{if eq .PageType "secrets"}} {{if eq .PageType "secrets"}}
{{template "shared/secrets/add_list" .}} {{template "shared/secrets/add_list" .}}
{{else if eq .PageType "runners"}}
{{template "shared/actions/runner_list" .}}
{{end}} {{end}}
</div> </div>

View File

@ -23,6 +23,9 @@
<div class="item"> <div class="item">
{{.locale.Tr "actions.actions"}} {{.locale.Tr "actions.actions"}}
<div class="menu"> <div class="menu">
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{AppSubUrl}}/user/settings/actions/runners">
{{.locale.Tr "actions.runners"}}
</a>
<a class="{{if .PageIsSharedSettingsSecrets}}active {{end}}item" href="{{AppSubUrl}}/user/settings/actions/secrets"> <a class="{{if .PageIsSharedSettingsSecrets}}active {{end}}item" href="{{AppSubUrl}}/user/settings/actions/secrets">
{{.locale.Tr "secrets.secrets"}} {{.locale.Tr "secrets.secrets"}}
</a> </a>

View File

@ -105,7 +105,7 @@
<label>{{.locale.Tr "settings.lookup_avatar_by_mail"}}</label> <label>{{.locale.Tr "settings.lookup_avatar_by_mail"}}</label>
</div> </div>
</div> </div>
<div class="field {{if .Err_Gravatar}}error{{end}}"> <div class="field gt-pl-4 {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{.locale.Tr "email"}}</label> <label for="gravatar">Avatar {{.locale.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}"> <input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}">
</div> </div>
@ -118,7 +118,7 @@
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field gt-pl-4">
<label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label> <label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> <input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div> </div>

View File

@ -0,0 +1,5 @@
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings runners")}}
<div class="user-setting-content">
{{template "shared/actions/runner_edit" .}}
</div>
{{template "user/settings/layout_footer" .}}

View File

@ -74,12 +74,15 @@
--color-secondary-alpha-90: #dededee1; --color-secondary-alpha-90: #dededee1;
--color-secondary-hover: var(--color-secondary-dark-1); --color-secondary-hover: var(--color-secondary-dark-1);
--color-secondary-active: var(--color-secondary-dark-2); --color-secondary-active: var(--color-secondary-dark-2);
/* console colors */ /* console colors - used for actions console and console files */
--color-console-fg: #ffffff; --color-console-fg: #eeeff2;
--color-console-bg: #252a2f; --color-console-fg-subtle: #959cab;
--color-console-border: #ffffff16; --color-console-bg: #262936;
--color-console-border: #383c47;
--color-console-hover-bg: #ffffff16; --color-console-hover-bg: #ffffff16;
--color-console-active-bg: #353a3f; --color-console-active-bg: #454a57;
--color-console-menu-bg: #383c47;
--color-console-menu-border: #5c6374;
/* named colors */ /* named colors */
--color-red: #db2828; --color-red: #db2828;
--color-orange: #f2711c; --color-orange: #f2711c;
@ -553,7 +556,7 @@ a.label,
color: var(--color-text-light-3); color: var(--color-text-light-3);
} }
.ui.menu .item::before { .ui.menu .item::before, .ui.vertical.menu .item::before {
background: var(--color-secondary); background: var(--color-secondary);
} }
@ -603,6 +606,7 @@ a.label,
.ui.dropdown .menu .active.item { .ui.dropdown .menu .active.item {
color: var(--color-text); color: var(--color-text);
background: var(--color-active); background: var(--color-active);
border-radius: 0;
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
} }
@ -847,6 +851,7 @@ a.label,
.ui.table > thead > tr > th { .ui.table > thead > tr > th {
background: var(--color-box-header); background: var(--color-box-header);
border-color: var(--color-secondary);
color: var(--color-text); color: var(--color-text);
} }

View File

@ -3419,14 +3419,6 @@ tbody.commit-list {
background: var(--color-card); background: var(--color-card);
} }
.pinned-issue-card .meta a {
color: inherit;
}
.pinned-issue-card .meta a:hover {
color: var(--color-primary);
}
.pinned-issue-icon, .pinned-issue-icon,
.pinned-issue-unpin { .pinned-issue-unpin {
margin-top: 1px; margin-top: 1px;

View File

@ -60,12 +60,15 @@
--color-secondary-alpha-90: #525767e1; --color-secondary-alpha-90: #525767e1;
--color-secondary-hover: var(--color-secondary-light-1); --color-secondary-hover: var(--color-secondary-light-1);
--color-secondary-active: var(--color-secondary-light-2); --color-secondary-active: var(--color-secondary-light-2);
/* console colors */ /* console colors - used for actions console and console files */
--color-console-fg: #ffffff; --color-console-fg: #eeeff2;
--color-console-fg-subtle: #959cab;
--color-console-bg: #262936; --color-console-bg: #262936;
--color-console-border: #ffffff16; --color-console-border: #383c47;
--color-console-hover-bg: #ffffff16; --color-console-hover-bg: #ffffff16;
--color-console-active-bg: #383c47; --color-console-active-bg: #454a57;
--color-console-menu-bg: #383c47;
--color-console-menu-border: #5c6374;
/* named colors */ /* named colors */
--color-red: #cc4848; --color-red: #cc4848;
--color-orange: #cc580c; --color-orange: #cc580c;

View File

@ -1,10 +1,10 @@
<template> <template>
<ol class="diff-detail-box diff-stats gt-m-0" ref="root" v-if="fileListIsVisible"> <ol class="diff-detail-box diff-stats gt-m-0" ref="root" v-if="store.fileListIsVisible">
<li v-for="file in files" :key="file.NameHash"> <li v-for="file in store.files" :key="file.NameHash">
<div class="gt-font-semibold gt-df gt-ac pull-right"> <div class="gt-font-semibold gt-df gt-ac pull-right">
<span v-if="file.IsBin" class="gt-ml-1 gt-mr-3">{{ binaryFileMessage }}</span> <span v-if="file.IsBin" class="gt-ml-1 gt-mr-3">{{ store.binaryFileMessage }}</span>
{{ file.IsBin ? '' : file.Addition + file.Deletion }} {{ file.IsBin ? '' : file.Addition + file.Deletion }}
<span v-if="!file.IsBin" class="diff-stats-bar gt-mx-3" :data-tooltip-content="statisticsMessage.replace('%d', (file.Addition + file.Deletion)).replace('%d', file.Addition).replace('%d', file.Deletion)"> <span v-if="!file.IsBin" class="diff-stats-bar gt-mx-3" :data-tooltip-content="store.statisticsMessage.replace('%d', (file.Addition + file.Deletion)).replace('%d', file.Addition).replace('%d', file.Deletion)">
<div class="diff-stats-add-bar" :style="{ 'width': diffStatsWidth(file.Addition, file.Deletion) }"/> <div class="diff-stats-add-bar" :style="{ 'width': diffStatsWidth(file.Addition, file.Deletion) }"/>
</span> </span>
</div> </div>
@ -12,22 +12,21 @@
<span :class="['status', diffTypeToString(file.Type)]" :data-tooltip-content="diffTypeToString(file.Type)">&nbsp;</span> <span :class="['status', diffTypeToString(file.Type)]" :data-tooltip-content="diffTypeToString(file.Type)">&nbsp;</span>
<a class="file gt-mono" :href="'#diff-' + file.NameHash">{{ file.Name }}</a> <a class="file gt-mono" :href="'#diff-' + file.NameHash">{{ file.Name }}</a>
</li> </li>
<li v-if="isIncomplete" id="diff-too-many-files-stats" class="gt-pt-2"> <li v-if="store.isIncomplete" class="gt-pt-2">
<span class="file gt-df gt-ac gt-sb">{{ tooManyFilesMessage }} <span class="file gt-df gt-ac gt-sb">{{ store.tooManyFilesMessage }}
<a :class="['ui', 'basic', 'tiny', 'button', isLoadingNewData === true ? 'disabled' : '']" id="diff-show-more-files-stats" @click.stop="loadMoreData">{{ showMoreMessage }}</a> <a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a>
</span> </span>
</li> </li>
</ol> </ol>
</template> </template>
<script> <script>
import {doLoadMoreFiles} from '../features/repo-diff.js'; import {loadMoreFiles} from '../features/repo-diff.js';
import {diffTreeStore} from '../modules/stores.js';
const {pageData} = window.config;
export default { export default {
data: () => { data: () => {
return pageData.diffFileInfo; return {store: diffTreeStore()};
}, },
mounted() { mounted() {
document.getElementById('show-file-list-btn').addEventListener('click', this.toggleFileList); document.getElementById('show-file-list-btn').addEventListener('click', this.toggleFileList);
@ -37,7 +36,7 @@ export default {
}, },
methods: { methods: {
toggleFileList() { toggleFileList() {
this.fileListIsVisible = !this.fileListIsVisible; this.store.fileListIsVisible = !this.store.fileListIsVisible;
}, },
diffTypeToString(pType) { diffTypeToString(pType) {
const diffTypes = { const diffTypes = {
@ -53,10 +52,7 @@ export default {
return `${adds / (adds + dels) * 100}%`; return `${adds / (adds + dels) * 100}%`;
}, },
loadMoreData() { loadMoreData() {
this.isLoadingNewData = true; loadMoreFiles(this.store.linkLoadMore);
doLoadMoreFiles(this.link, this.diffEnd, () => {
this.isLoadingNewData = false;
});
} }
}, },
}; };

View File

@ -1,42 +1,33 @@
<template> <template>
<div <div v-if="store.fileTreeIsVisible" class="gt-mr-3 gt-mt-3 diff-detail-box">
v-if="fileTreeIsVisible"
class="gt-mr-3 gt-mt-3 diff-detail-box"
>
<!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
<div class="ui list"> <div class="ui list">
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/> <DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/>
</div> </div>
<div v-if="isIncomplete" id="diff-too-many-files-stats" class="gt-pt-2"> <div v-if="store.isIncomplete" class="gt-pt-2">
<span class="gt-mr-2">{{ tooManyFilesMessage }}</span><a :class="['ui', 'basic', 'tiny', 'button', isLoadingNewData === true ? 'disabled' : '']" id="diff-show-more-files-stats" @click.stop="loadMoreData">{{ showMoreMessage }}</a> <a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import DiffFileTreeItem from './DiffFileTreeItem.vue'; import DiffFileTreeItem from './DiffFileTreeItem.vue';
import {doLoadMoreFiles} from '../features/repo-diff.js'; import {loadMoreFiles} from '../features/repo-diff.js';
import {toggleElem} from '../utils/dom.js'; import {toggleElem} from '../utils/dom.js';
import {DiffTreeStore} from '../modules/stores.js'; import {diffTreeStore} from '../modules/stores.js';
import {setFileFolding} from '../features/file-fold.js'; import {setFileFolding} from '../features/file-fold.js';
const {pageData} = window.config;
const LOCAL_STORAGE_KEY = 'diff_file_tree_visible'; const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
export default { export default {
components: {DiffFileTreeItem}, components: {DiffFileTreeItem},
data: () => { data: () => {
const fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) === 'true'; return {store: diffTreeStore()};
pageData.diffFileInfo.fileTreeIsVisible = fileTreeIsVisible;
return {
...pageData.diffFileInfo,
store: DiffTreeStore,
};
}, },
computed: { computed: {
fileTree() { fileTree() {
const result = []; const result = [];
for (const file of this.files) { for (const file of this.store.files) {
// Split file into directories // Split file into directories
const splits = file.Name.split('/'); const splits = file.Name.split('/');
let index = 0; let index = 0;
@ -98,9 +89,7 @@ export default {
} }
}, },
mounted() { mounted() {
// replace the pageData.diffFileInfo.files with our watched data so we get updates this.store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) === 'true';
pageData.diffFileInfo.files = this.files;
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', this.toggleVisibility); document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', this.toggleVisibility);
this.hashChangeListener = () => { this.hashChangeListener = () => {
@ -124,12 +113,12 @@ export default {
} }
}, },
toggleVisibility() { toggleVisibility() {
this.updateVisibility(!this.fileTreeIsVisible); this.updateVisibility(!this.store.fileTreeIsVisible);
}, },
updateVisibility(visible) { updateVisibility(visible) {
this.fileTreeIsVisible = visible; this.store.fileTreeIsVisible = visible;
localStorage.setItem(LOCAL_STORAGE_KEY, this.fileTreeIsVisible); localStorage.setItem(LOCAL_STORAGE_KEY, this.store.fileTreeIsVisible);
this.updateState(this.fileTreeIsVisible); this.updateState(this.store.fileTreeIsVisible);
}, },
updateState(visible) { updateState(visible) {
const btn = document.querySelector('.diff-toggle-file-tree-button'); const btn = document.querySelector('.diff-toggle-file-tree-button');
@ -142,12 +131,7 @@ export default {
toggleElem(toHide, visible); toggleElem(toHide, visible);
}, },
loadMoreData() { loadMoreData() {
this.isLoadingNewData = true; loadMoreFiles(this.store.linkLoadMore);
doLoadMoreFiles(this.link, this.diffEnd, () => {
this.isLoadingNewData = false;
const {pageData} = window.config;
this.diffEnd = pageData.diffFileInfo.diffEnd;
});
}, },
}, },
}; };

View File

@ -40,7 +40,7 @@
<script> <script>
import {SvgIcon} from '../svg.js'; import {SvgIcon} from '../svg.js';
import {DiffTreeStore} from '../modules/stores.js'; import {diffTreeStore} from '../modules/stores.js';
export default { export default {
components: {SvgIcon}, components: {SvgIcon},
@ -56,7 +56,7 @@ export default {
}, },
}, },
data: () => ({ data: () => ({
store: DiffTreeStore, store: diffTreeStore(),
collapsed: false, collapsed: false,
}), }),
methods: { methods: {

View File

@ -60,14 +60,38 @@
<div class="action-view-right"> <div class="action-view-right">
<div class="job-info-header"> <div class="job-info-header">
<h3 class="job-info-header-title"> <div class="job-info-header-left">
{{ currentJob.title }} <h3 class="job-info-header-title">
</h3> {{ currentJob.title }}
<p class="job-info-header-detail"> </h3>
{{ currentJob.detail }} <p class="job-info-header-detail">
</p> {{ currentJob.detail }}
</p>
</div>
<div class="job-info-header-right">
<div class="ui top right pointing dropdown custom jump item" @click.stop="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
<button class="ui button button-ghost gt-p-3">
<SvgIcon name="octicon-gear" :size="18"/>
</button>
<div class="menu transition action-job-menu" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
<a class="item" @click="toggleTimeDisplay('seconds')">
<i class="icon"><SvgIcon v-show="timeVisible['log-time-seconds']" name="octicon-check"/></i>
{{ locale.showLogSeconds }}
</a>
<a class="item" @click="toggleTimeDisplay('stamp')">
<i class="icon"><SvgIcon v-show="timeVisible['log-time-stamp']" name="octicon-check"/></i>
{{ locale.showTimeStamps }}
</a>
<div class="divider"/>
<a class="item" @click="toggleFullScreen()">
<i class="icon"><SvgIcon v-show="isFullScreen" name="octicon-check"/></i>
{{ locale.showFullScreen }}
</a>
</div>
</div>
</div>
</div> </div>
<div class="job-step-container"> <div class="job-step-container" ref="steps">
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i"> <div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
<div class="job-step-summary" @click.stop="toggleStepLogs(i)" :class="currentJobStepsStates[i].expanded ? 'selected' : ''"> <div class="job-step-summary" @click.stop="toggleStepLogs(i)" :class="currentJobStepsStates[i].expanded ? 'selected' : ''">
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon <!-- If the job is done and the job step log is loaded for the first time, show the loading icon
@ -81,7 +105,8 @@
<span class="step-summary-duration">{{ jobStep.duration }}</span> <span class="step-summary-duration">{{ jobStep.duration }}</span>
</div> </div>
<!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM --> <!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM,
use native DOM elements for "log line" to improve performance, Vue is not suitable for managing so many reactive elements. -->
<div class="job-step-logs" ref="logs" v-show="currentJobStepsStates[i].expanded"/> <div class="job-step-logs" ref="logs" v-show="currentJobStepsStates[i].expanded"/>
</div> </div>
</div> </div>
@ -95,6 +120,8 @@ import {SvgIcon} from '../svg.js';
import ActionRunStatus from './ActionRunStatus.vue'; import ActionRunStatus from './ActionRunStatus.vue';
import {createApp} from 'vue'; import {createApp} from 'vue';
import AnsiToHTML from 'ansi-to-html'; import AnsiToHTML from 'ansi-to-html';
import {toggleElem} from '../utils/dom.js';
import {getCurrentLocale} from '../utils.js';
const {csrfToken} = window.config; const {csrfToken} = window.config;
@ -121,6 +148,12 @@ const sfc = {
currentJobStepsStates: [], currentJobStepsStates: [],
artifacts: [], artifacts: [],
onHoverRerunIndex: -1, onHoverRerunIndex: -1,
menuVisible: false,
isFullScreen: false,
timeVisible: {
'log-time-stamp': false,
'log-time-seconds': false,
},
// provided by backend // provided by backend
run: { run: {
@ -173,6 +206,11 @@ const sfc = {
// load job data and then auto-reload periodically // load job data and then auto-reload periodically
this.loadJob(); this.loadJob();
this.intervalID = setInterval(this.loadJob, 1000); this.intervalID = setInterval(this.loadJob, 1000);
document.body.addEventListener('click', this.closeDropdown);
},
beforeUnmount() {
document.body.removeEventListener('click', this.closeDropdown);
}, },
unmounted() { unmounted() {
@ -240,7 +278,7 @@ const sfc = {
this.fetchPost(`${this.run.link}/approve`); this.fetchPost(`${this.run.link}/approve`);
}, },
createLogLine(line) { createLogLine(line, startTime) {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('job-log-line'); div.classList.add('job-log-line');
div._jobLogTime = line.timestamp; div._jobLogTime = line.timestamp;
@ -250,21 +288,35 @@ const sfc = {
lineNumber.textContent = line.index; lineNumber.textContent = line.index;
div.append(lineNumber); div.append(lineNumber);
// TODO: Support displaying time optionally // for "Show timestamps"
const logTimeStamp = document.createElement('span');
logTimeStamp.className = 'log-time-stamp';
const date = new Date(parseFloat(line.timestamp * 1000));
const timeStamp = date.toLocaleString(getCurrentLocale(), {timeZoneName: 'short'});
logTimeStamp.textContent = timeStamp;
toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']);
// for "Show seconds"
const logTimeSeconds = document.createElement('span');
logTimeSeconds.className = 'log-time-seconds';
const seconds = Math.floor(parseFloat(line.timestamp) - parseFloat(startTime));
logTimeSeconds.textContent = `${seconds}s`;
toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);
const logMessage = document.createElement('div'); const logMessage = document.createElement('span');
logMessage.className = 'log-msg'; logMessage.className = 'log-msg';
logMessage.innerHTML = ansiLogToHTML(line.message); logMessage.innerHTML = ansiLogToHTML(line.message);
div.append(logTimeStamp);
div.append(logMessage); div.append(logMessage);
div.append(logTimeSeconds);
return div; return div;
}, },
appendLogs(stepIndex, logLines) { appendLogs(stepIndex, logLines, startTime) {
for (const line of logLines) { for (const line of logLines) {
// TODO: group support: ##[group]GroupTitle , ##[endgroup] // TODO: group support: ##[group]GroupTitle , ##[endgroup]
const el = this.getLogsContainer(stepIndex); const el = this.getLogsContainer(stepIndex);
el.append(this.createLogLine(line)); el.append(this.createLogLine(line, startTime));
} }
}, },
@ -309,7 +361,7 @@ const sfc = {
for (const logs of response.logs.stepsLog) { for (const logs of response.logs.stepsLog) {
// save the cursor, it will be passed to backend next time // save the cursor, it will be passed to backend next time
this.currentJobStepsStates[logs.step].cursor = logs.cursor; this.currentJobStepsStates[logs.step].cursor = logs.cursor;
this.appendLogs(logs.step, logs.lines); this.appendLogs(logs.step, logs.lines, logs.started);
} }
if (this.run.done && this.intervalID) { if (this.run.done && this.intervalID) {
@ -335,6 +387,46 @@ const sfc = {
isDone(status) { isDone(status) {
return ['success', 'skipped', 'failure', 'cancelled'].includes(status); return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
},
closeDropdown() {
if (this.menuVisible) this.menuVisible = false;
},
// show at most one of log seconds and timestamp (can be both invisible)
toggleTimeDisplay(type) {
const toToggleTypes = [];
const other = type === 'seconds' ? 'stamp' : 'seconds';
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
toToggleTypes.push(type);
if (this.timeVisible[`log-time-${type}`] && this.timeVisible[`log-time-${other}`]) {
this.timeVisible[`log-time-${other}`] = false;
toToggleTypes.push(other);
}
for (const toToggle of toToggleTypes) {
for (const el of this.$refs.steps.querySelectorAll(`.log-time-${toToggle}`)) {
toggleElem(el, this.timeVisible[`log-time-${toToggle}`]);
}
}
},
toggleFullScreen() {
this.isFullScreen = !this.isFullScreen;
const fullScreenEl = document.querySelector('.action-view-right');
const outerEl = document.querySelector('.full.height');
const actionBodyEl = document.querySelector('.action-view-body');
const headerEl = document.querySelector('.ui.main.menu');
const contentEl = document.querySelector('.page-content.repository');
const footerEl = document.querySelector('.page-footer');
toggleElem(headerEl, !this.isFullScreen);
toggleElem(contentEl, !this.isFullScreen);
toggleElem(footerEl, !this.isFullScreen);
// move .action-view-right to new parent
if (this.isFullScreen) {
outerEl.append(fullScreenEl);
} else {
actionBodyEl.append(fullScreenEl);
}
} }
}, },
}; };
@ -360,6 +452,9 @@ export function initRepositoryActionView() {
rerun: el.getAttribute('data-locale-rerun'), rerun: el.getAttribute('data-locale-rerun'),
artifactsTitle: el.getAttribute('data-locale-artifacts-title'), artifactsTitle: el.getAttribute('data-locale-artifacts-title'),
rerun_all: el.getAttribute('data-locale-rerun-all'), rerun_all: el.getAttribute('data-locale-rerun-all'),
showTimeStamps: el.getAttribute('data-locale-show-timestamps'),
showLogSeconds: el.getAttribute('data-locale-show-log-seconds'),
showFullScreen: el.getAttribute('data-locale-show-full-screen'),
status: { status: {
unknown: el.getAttribute('data-locale-status-unknown'), unknown: el.getAttribute('data-locale-status-unknown'),
waiting: el.getAttribute('data-locale-status-waiting'), waiting: el.getAttribute('data-locale-status-waiting'),
@ -369,7 +464,7 @@ export function initRepositoryActionView() {
cancelled: el.getAttribute('data-locale-status-cancelled'), cancelled: el.getAttribute('data-locale-status-cancelled'),
skipped: el.getAttribute('data-locale-status-skipped'), skipped: el.getAttribute('data-locale-status-skipped'),
blocked: el.getAttribute('data-locale-status-blocked'), blocked: el.getAttribute('data-locale-status-blocked'),
} },
} }
}); });
view.mount(el); view.mount(el);
@ -567,21 +662,95 @@ export function ansiLogToHTML(line) {
.action-view-right { .action-view-right {
flex: 1; flex: 1;
color: var(--color-secondary-dark-3); color: var(--color-console-fg-subtle);
max-height: 100%; max-height: 100%;
width: 70%; width: 70%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
/* begin fomantic button overrides */
.action-view-right .ui.button,
.action-view-right .ui.button:focus {
background: transparent;
color: var(--color-console-fg-subtle);
}
.action-view-right .ui.button:hover {
background: var(--color-console-hover-bg);
color: var(--color-console-fg);
}
.action-view-right .ui.button:active {
background: var(--color-console-active-bg);
color: var(--color-console-fg);
}
/* end fomantic button overrides */
/* begin fomantic dropdown menu overrides */
.action-view-right .ui.dropdown .menu {
background: var(--color-console-menu-bg);
border-color: var(--color-console-menu-border);
}
.action-view-right .ui.dropdown .menu > .item {
color: var(--color-console-fg);
}
.action-view-right .ui.dropdown .menu > .item:hover {
color: var(--color-console-fg);
background: var(--color-console-hover-bg);
}
.action-view-right .ui.dropdown .menu > .item:active {
color: var(--color-console-fg);
background: var(--color-console-active-bg);
}
.action-view-right .ui.dropdown .menu > .divider {
border-top-color: var(--color-console-menu-border);
}
.action-view-right .ui.pointing.dropdown > .menu:not(.hidden)::after {
background: var(--color-console-menu-bg);
box-shadow: -1px -1px 0 0 var(--color-console-menu-border);
}
/* end fomantic dropdown menu overrides */
/* selectors here are intentionally exact to only match fullscreen */
.full.height > .action-view-right {
width: 100%;
height: 100%;
padding: 0;
border-radius: 0;
}
.full.height > .action-view-right > .job-info-header {
border-radius: 0;
}
.full.height > .action-view-right > .job-step-container {
height: calc(100% - 60px);
border-radius: 0;
}
.job-info-header { .job-info-header {
padding: 10px; display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px;
border-bottom: 1px solid var(--color-console-border); border-bottom: 1px solid var(--color-console-border);
background-color: var(--color-console-bg); background-color: var(--color-console-bg);
position: sticky; position: sticky;
top: 0; top: 0;
border-radius: var(--border-radius) var(--border-radius) 0 0; border-radius: var(--border-radius) var(--border-radius) 0 0;
height: 60px; height: 60px;
z-index: 1;
} }
.job-info-header .job-info-header-title { .job-info-header .job-info-header-title {
@ -591,7 +760,7 @@ export function ansiLogToHTML(line) {
} }
.job-info-header .job-info-header-detail { .job-info-header .job-info-header-detail {
color: var(--color-secondary-dark-3); color: var(--color-console-fg-subtle);
font-size: 12px; font-size: 12px;
} }
@ -676,14 +845,20 @@ export function ansiLogToHTML(line) {
background-color: var(--color-console-hover-bg); background-color: var(--color-console-hover-bg);
} }
.job-step-section .job-step-logs .job-log-line .line-num { /* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */
.job-log-line .line-num, .log-time-seconds {
width: 48px; width: 48px;
color: var(--color-grey-light); color: var(--color-grey-light);
text-align: right; text-align: right;
user-select: none; user-select: none;
} }
.job-step-section .job-step-logs .job-log-line .log-time { .log-time-seconds {
padding-right: 2px;
}
.job-log-line .log-time,
.log-time-stamp {
color: var(--color-grey-light); color: var(--color-grey-light);
margin-left: 10px; margin-left: 10px;
white-space: nowrap; white-space: nowrap;

View File

@ -5,7 +5,7 @@ import {initDiffFileTree} from './repo-diff-filetree.js';
import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js'; import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
const {csrfToken} = window.config; const {csrfToken, pageData} = window.config;
function initRepoDiffReviewButton() { function initRepoDiffReviewButton() {
const $reviewBox = $('#review-box'); const $reviewBox = $('#review-box');
@ -119,37 +119,29 @@ function onShowMoreFiles() {
countAndUpdateViewedFiles(); countAndUpdateViewedFiles();
} }
export function doLoadMoreFiles(link, diffEnd, callback) { export function loadMoreFiles(url) {
const url = `${link}?skip-to=${diffEnd}&file-only=true`;
loadMoreFiles(url, callback);
}
function loadMoreFiles(url, callback) {
const $target = $('a#diff-show-more-files'); const $target = $('a#diff-show-more-files');
if ($target.hasClass('disabled')) { if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) {
callback();
return; return;
} }
pageData.diffFileInfo.isLoadingNewData = true;
$target.addClass('disabled'); $target.addClass('disabled');
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url, url,
}).done((resp) => { }).done((resp) => {
if (!resp) { const $resp = $(resp);
$target.removeClass('disabled'); // the response is a full HTML page, we need to extract the relevant contents:
callback(resp); // 1. append the newly loaded file list items to the existing list
return; $('#diff-incomplete').replaceWith($resp.find('#diff-file-boxes').children());
} // 2. re-execute the script to append the newly loaded items to the JS variables to refresh the DiffFileTree
$('#diff-incomplete').replaceWith($(resp).find('#diff-file-boxes').children()); $('body').append($resp.find('script#diff-data-script'));
// By simply rerunning the script we add the new data to our existing
// pagedata object. this triggers vue and the filetree and filelist will
// render the new elements.
$('body').append($(resp).find('script#diff-data-script'));
onShowMoreFiles(); onShowMoreFiles();
callback(resp); }).always(() => {
}).fail(() => {
$target.removeClass('disabled'); $target.removeClass('disabled');
callback(); pageData.diffFileInfo.isLoadingNewData = false;
}); });
} }
@ -158,7 +150,8 @@ function initRepoDiffShowMore() {
e.preventDefault(); e.preventDefault();
const $target = $(e.target); const $target = $(e.target);
loadMoreFiles($target.data('href'), () => {}); const linkLoadMore = $target.attr('data-href');
loadMoreFiles(linkLoadMore);
}); });
$(document).on('click', 'a.diff-load-button', (e) => { $(document).on('click', 'a.diff-load-button', (e) => {

View File

@ -1,57 +0,0 @@
import {joinPaths, parseUrl} from '../utils.js';
const {useServiceWorker, assetUrlPrefix, assetVersionEncoded} = window.config;
const cachePrefix = 'static-cache-v'; // actual version is set in the service worker script
const workerUrl = `${joinPaths(assetUrlPrefix, 'serviceworker.js')}?v=${assetVersionEncoded}`;
async function unregisterAll() {
for (const registration of await navigator.serviceWorker.getRegistrations()) {
if (registration.active) await registration.unregister();
}
}
async function unregisterOtherWorkers() {
for (const registration of await navigator.serviceWorker.getRegistrations()) {
const scriptPath = parseUrl(registration.active?.scriptURL || '').pathname;
const workerPath = parseUrl(workerUrl).pathname;
if (scriptPath !== workerPath) await registration.unregister();
}
}
async function invalidateCache() {
for (const key of await caches.keys()) {
if (key.startsWith(cachePrefix)) caches.delete(key);
}
}
async function checkCacheValidity() {
const cacheKey = assetVersionEncoded;
const storedCacheKey = localStorage.getItem('staticCacheKey');
// invalidate cache if it belongs to a different gitea version
if (cacheKey && storedCacheKey !== cacheKey) {
await invalidateCache();
localStorage.setItem('staticCacheKey', cacheKey);
}
}
export async function initServiceWorker() {
if (!('serviceWorker' in navigator)) return;
if (useServiceWorker) {
// unregister all service workers where scriptURL does not match the current one
await unregisterOtherWorkers();
try {
// the spec strictly requires it to be same-origin so the AssetUrlPrefix should contain AppSubUrl
await checkCacheValidity();
await navigator.serviceWorker.register(workerUrl);
} catch (err) {
console.error(err);
await invalidateCache();
await unregisterAll();
}
} else {
await invalidateCache();
await unregisterAll();
}
}

View File

@ -11,7 +11,6 @@ import {initHeatmap} from './features/heatmap.js';
import {initImageDiff} from './features/imagediff.js'; import {initImageDiff} from './features/imagediff.js';
import {initRepoMigration} from './features/repo-migration.js'; import {initRepoMigration} from './features/repo-migration.js';
import {initRepoProject} from './features/repo-projects.js'; import {initRepoProject} from './features/repo-projects.js';
import {initServiceWorker} from './features/serviceworker.js';
import {initTableSort} from './features/tablesort.js'; import {initTableSort} from './features/tablesort.js';
import {initAdminUserListSearchForm} from './features/admin/users.js'; import {initAdminUserListSearchForm} from './features/admin/users.js';
import {initAdminConfigs} from './features/admin/config.js'; import {initAdminConfigs} from './features/admin/config.js';
@ -116,7 +115,6 @@ onDomReady(() => {
initImageDiff(); initImageDiff();
initMarkupAnchors(); initMarkupAnchors();
initMarkupContent(); initMarkupContent();
initServiceWorker();
initSshKeyFormParser(); initSshKeyFormParser();
initStopwatch(); initStopwatch();
initTableSort(); initTableSort();

View File

@ -1,5 +1,10 @@
import {reactive} from 'vue'; import {reactive} from 'vue';
export const DiffTreeStore = reactive({ let diffTreeStoreReactive;
selectedItem: '', export function diffTreeStore() {
}); if (!diffTreeStoreReactive) {
diffTreeStoreReactive = reactive(window.config.pageData.diffFileInfo);
window.config.pageData.diffFileInfo = diffTreeStoreReactive;
}
return diffTreeStoreReactive;
}

View File

@ -1,23 +0,0 @@
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
const cacheName = 'static-cache-v2';
// disable workbox debug logging in development, remove when debugging the service worker
self.__WB_DISABLE_DEV_LOGS = true;
// see https://developer.mozilla.org/en-US/docs/Web/API/RequestDestination for possible values
const cachedDestinations = new Set([
'font',
'manifest',
'paintworklet',
'script',
'sharedworker',
'style',
'worker',
]);
registerRoute(
({request}) => cachedDestinations.has(request.destination),
new StaleWhileRevalidate({cacheName}),
);

View File

@ -26,6 +26,7 @@ import octiconEye from '../../public/img/svg/octicon-eye.svg';
import octiconFile from '../../public/img/svg/octicon-file.svg'; import octiconFile from '../../public/img/svg/octicon-file.svg';
import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg'; import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg';
import octiconFilter from '../../public/img/svg/octicon-filter.svg'; import octiconFilter from '../../public/img/svg/octicon-filter.svg';
import octiconGear from '../../public/img/svg/octicon-gear.svg';
import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg';
import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg'; import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg'; import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
@ -94,6 +95,7 @@ const svgs = {
'octicon-file': octiconFile, 'octicon-file': octiconFile,
'octicon-file-directory-fill': octiconFileDirectoryFill, 'octicon-file-directory-fill': octiconFileDirectoryFill,
'octicon-filter': octiconFilter, 'octicon-filter': octiconFilter,
'octicon-gear': octiconGear,
'octicon-git-branch': octiconGitBranch, 'octicon-git-branch': octiconGitBranch,
'octicon-git-merge': octiconGitMerge, 'octicon-git-merge': octiconGitMerge,
'octicon-git-pull-request': octiconGitPullRequest, 'octicon-git-pull-request': octiconGitPullRequest,
@ -132,7 +134,7 @@ const svgs = {
'octicon-tag': octiconTag, 'octicon-tag': octiconTag,
'octicon-triangle-down': octiconTriangleDown, 'octicon-triangle-down': octiconTriangleDown,
'octicon-x': octiconX, 'octicon-x': octiconX,
'octicon-x-circle-fill': octiconXCircleFill 'octicon-x-circle-fill': octiconXCircleFill,
}; };
// TODO: use a more general approach to access SVG icons. // TODO: use a more general approach to access SVG icons.

View File

@ -60,7 +60,7 @@ export function parseUrl(str) {
} }
// return current locale chosen by user // return current locale chosen by user
function getCurrentLocale() { export function getCurrentLocale() {
return document.documentElement.lang; return document.documentElement.lang;
} }

View File

@ -62,9 +62,6 @@ export default {
fileURLToPath(new URL('web_src/js/standalone/swagger.js', import.meta.url)), fileURLToPath(new URL('web_src/js/standalone/swagger.js', import.meta.url)),
fileURLToPath(new URL('web_src/css/standalone/swagger.css', import.meta.url)), fileURLToPath(new URL('web_src/css/standalone/swagger.css', import.meta.url)),
], ],
serviceworker: [
fileURLToPath(new URL('web_src/js/serviceworker.js', import.meta.url)),
],
'eventsource.sharedworker': [ 'eventsource.sharedworker': [
fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.js', import.meta.url)), fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.js', import.meta.url)),
], ],
@ -73,11 +70,7 @@ export default {
devtool: false, devtool: false,
output: { output: {
path: fileURLToPath(new URL('public', import.meta.url)), path: fileURLToPath(new URL('public', import.meta.url)),
filename: ({chunk}) => { filename: () => 'js/[name].js',
// serviceworker can only manage assets below it's script's directory so
// we have to put it in / instead of /js/
return chunk.name === 'serviceworker' ? '[name].js' : 'js/[name].js';
},
chunkFilename: ({chunk}) => { chunkFilename: ({chunk}) => {
const language = (/monaco.*languages?_.+?_(.+?)_/.exec(chunk.id) || [])[1]; const language = (/monaco.*languages?_.+?_(.+?)_/.exec(chunk.id) || [])[1];
return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`; return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`;