loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add `<overflow-menu>`, rename webcomponents (#29400)

1. Add `<overflow-menu>` web component
2. Rename `<gitea-origin-url>` to `<origin-url>` and make filenames
match.

<img width="439" alt="image"
src="https://github.com/go-gitea/gitea/assets/115237/2fbe4ca4-110b-4ad2-8e17-c1e116ccbd74">

<img width="444" alt="Screenshot 2024-03-02 at 21 36 52"
src="https://github.com/go-gitea/gitea/assets/115237/aa8f786e-dc8c-4030-b12d-7cfb74bdfd6e">

<img width="537" alt="Screenshot 2024-03-03 at 03 05 06"
src="https://github.com/go-gitea/gitea/assets/115237/fddd50aa-adf1-4b4b-bd7f-caf30c7b2245">

![image](https://github.com/go-gitea/gitea/assets/115237/0f43770c-834c-4a05-8e3d-d30eb8653786)

![image](https://github.com/go-gitea/gitea/assets/115237/4b4c6bd7-843f-4f49-808f-6b3aed5e9f9a)

TODO:

- [x] Check if removal of `requestAnimationFrame` is possible to avoid
flash of content. Likely needs a `MutationObserver`.
- [x] Hide tippy when button is removed from DOM.
- [x] ~~Implement right-aligned items
(https://github.com/go-gitea/gitea/pull/28976)~~. Not going to do it.
- [x] Clean up CSS so base element has no background and add background
via tailwind instead.
- [x] Use it for org and user page.

---------

Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 256a1eeb9a67b18c62a10f5909b584b7b220848a)

Conflicts:
options/locale/locale_en-US.ini
templates/package/content/cargo.tmpl
templates/package/content/cran.tmpl
templates/package/content/debian.tmpl
templates/package/content/maven.tmpl

authored by

silverwind
Giteabot
wxiaoguang
and committed by
Earl Warren
5a16c9d9 b73ed152

+458 -288
+1 -1
modules/timeutil/datetime.go
··· 56 56 switch format { 57 57 case "short", "long": // date only 58 58 attrs = append(attrs, `month="`+format+`"`, `day="numeric"`) 59 - return template.HTML(fmt.Sprintf(`<gitea-absolute-date %s date="%s">%s</gitea-absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) 59 + return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) 60 60 case "full": // full date including time 61 61 attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`) 62 62 return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
+5 -5
modules/timeutil/datetime_test.go
··· 28 28 assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) 29 29 30 30 actual := DateTime("short", "invalid") 31 - assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</gitea-absolute-date>`, actual) 31 + assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</absolute-date>`, actual) 32 32 33 33 actual = DateTime("short", refTimeStr) 34 - assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</gitea-absolute-date>`, actual) 34 + assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</absolute-date>`, actual) 35 35 36 36 actual = DateTime("short", refTime) 37 - assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</gitea-absolute-date>`, actual) 37 + assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual) 38 38 39 39 actual = DateTime("short", refDateStr) 40 - assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</gitea-absolute-date>`, actual) 40 + assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</absolute-date>`, actual) 41 41 42 42 actual = DateTime("short", refTimeStamp) 43 - assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</gitea-absolute-date>`, actual) 43 + assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual) 44 44 45 45 actual = DateTime("full", refTimeStamp) 46 46 assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
+1 -1
options/locale/locale_en-US.ini
··· 586 586 team_no_units_error = Allow access to at least one repository section. 587 587 email_been_used = The email address is already used. 588 588 email_invalid = The email address is invalid. 589 - email_domain_is_not_allowed = The domain of user email <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected. 590 589 openid_been_used = The OpenID address "%s" is already used. 591 590 username_password_incorrect = Username or password is incorrect. 592 591 password_complexity = Password does not pass complexity requirements: ··· 803 802 gpg_token_required = You must provide a signature for the below token 804 803 gpg_token = Token 805 804 gpg_token_help = You can generate a signature using: 805 + gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig 806 806 gpg_token_signature = Armored GPG signature 807 807 key_signature_gpg_placeholder = Begins with "-----BEGIN PGP SIGNATURE-----" 808 808 verify_gpg_key_success = GPG key "%s" has been verified.
-6
package-lock.json
··· 15 15 "@github/text-expander-element": "2.6.1", 16 16 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", 17 17 "@primer/octicons": "19.8.0", 18 - "@webcomponents/custom-elements": "1.6.0", 19 18 "add-asset-webpack-plugin": "2.0.1", 20 19 "ansi_up": "6.0.2", 21 20 "asciinema-player": "3.7.0", ··· 2863 2862 "@webassemblyjs/ast": "1.12.1", 2864 2863 "@xtuc/long": "4.2.2" 2865 2864 } 2866 - }, 2867 - "node_modules/@webcomponents/custom-elements": { 2868 - "version": "1.6.0", 2869 - "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz", 2870 - "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==" 2871 2865 }, 2872 2866 "node_modules/@webpack-cli/configtest": { 2873 2867 "version": "2.1.1",
-1
package.json
··· 14 14 "@github/text-expander-element": "2.6.1", 15 15 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", 16 16 "@primer/octicons": "19.8.0", 17 - "@webcomponents/custom-elements": "1.6.0", 18 17 "add-asset-webpack-plugin": "2.0.1", 19 18 "ansi_up": "6.0.2", 20 19 "asciinema-player": "3.7.0",
+1
templates/base/head_script.tmpl
··· 41 41 remove_label_str: {{ctx.Locale.Tr "remove_label_str"}}, 42 42 modal_confirm: {{ctx.Locale.Tr "modal.confirm"}}, 43 43 modal_cancel: {{ctx.Locale.Tr "modal.cancel"}}, 44 + more_items: {{ctx.Locale.Tr "more_items"}}, 44 45 }, 45 46 }; 46 47 {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
+35 -8
templates/devtest/gitea-ui.tmpl
··· 105 105 </div> 106 106 107 107 <div> 108 - <h1>GiteaOriginUrl</h1> 109 - <div><gitea-origin-url data-url="test/url"></gitea-origin-url></div> 110 - <div><gitea-origin-url data-url="/test/url"></gitea-origin-url></div> 108 + <h1>&lt;origin-url&gt;</h1> 109 + <div><origin-url data-url="test/url"></origin-url></div> 110 + <div><origin-url data-url="/test/url"></origin-url></div> 111 + </div> 112 + 113 + <div> 114 + <h1>&lt;overflow-menu&gt;</h1> 115 + <overflow-menu class="ui secondary pointing tabular borderless menu"> 116 + <div class="overflow-menu-items"> 117 + <a class="active item">item</a> 118 + <a class="item">item 1</a> 119 + <a class="item">item 2</a> 120 + <a class="item">item 3</a> 121 + <a class="item">item 4</a> 122 + <a class="item">item 5</a> 123 + <a class="item">item 6</a> 124 + <a class="item">item 7</a> 125 + <a class="item">item 8</a> 126 + <a class="item">item 9</a> 127 + <a class="item">item 10</a> 128 + <a class="item">item 11</a> 129 + <a class="item">item 12</a> 130 + <a class="item">item 13</a> 131 + <a class="item">item 14</a> 132 + <a class="item">item 15</a> 133 + <a class="item">item 16</a> 134 + <a class="item">item 17</a> 135 + <a class="item">item 18</a> 136 + </div> 137 + </overflow-menu> 111 138 </div> 112 139 113 140 <div> 114 141 <h1>GiteaAbsoluteDate</h1> 115 - <div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="short"></gitea-absolute-date></div> 116 - <div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="long"></gitea-absolute-date></div> 117 - <div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric"></gitea-absolute-date></div> 118 - <div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div> 119 - <div><gitea-absolute-date date="2024-03-11T19:00:00-05:00" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div> 142 + <div><absolute-date date="2024-03-11" year="numeric" day="numeric" month="short"></absolute-date></div> 143 + <div><absolute-date date="2024-03-11" year="numeric" day="numeric" month="long"></absolute-date></div> 144 + <div><absolute-date date="2024-03-11" year="" day="numeric" month="numeric"></absolute-date></div> 145 + <div><absolute-date date="2024-03-11" year="" day="numeric" month="numeric" weekday="long"></absolute-date></div> 146 + <div><absolute-date date="2024-03-11T19:00:00-05:00" year="" day="numeric" month="numeric" weekday="long"></absolute-date></div> 120 147 <div class="tw-text-text-light-2">relative-time: <relative-time format="datetime" datetime="2024-03-11" year="" day="numeric" month="numeric"></relative-time></div> 121 148 </div> 122 149
+3 -3
templates/explore/navbar.tmpl
··· 1 - <div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> 2 - <div class="new-menu-inner"> 1 + <overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar"> 2 + <div class="overflow-menu-items tw-justify-center"> 3 3 <a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos"> 4 4 {{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}} 5 5 </a> ··· 17 17 </a> 18 18 {{end}} 19 19 </div> 20 - </div> 20 + </overflow-menu>
+35 -36
templates/org/menu.tmpl
··· 1 1 <div class="ui container"> 2 - <div class="ui secondary stackable pointing menu"> 3 - <a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}"> 4 - {{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}} 5 - {{if .RepoCount}} 6 - <div class="ui small label">{{.RepoCount}}</div> 2 + <overflow-menu class="ui secondary pointing tabular borderless menu"> 3 + <div class="overflow-menu-items"> 4 + <a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}"> 5 + {{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}} 6 + {{if .RepoCount}} 7 + <div class="ui small label">{{.RepoCount}}</div> 8 + {{end}} 9 + </a> 10 + {{if .CanReadProjects}} 11 + <a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects"> 12 + {{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}} 13 + {{if .ProjectCount}} 14 + <div class="ui small label">{{.ProjectCount}}</div> 15 + {{end}} 16 + </a> 7 17 {{end}} 8 - </a> 9 - {{if .CanReadProjects}} 10 - <a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects"> 11 - {{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}} 12 - {{if .ProjectCount}} 13 - <div class="ui small label">{{.ProjectCount}}</div> 18 + {{if and .IsPackageEnabled .CanReadPackages}} 19 + <a class="{{if .IsPackagesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/packages"> 20 + {{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}} 21 + </a> 14 22 {{end}} 15 - </a> 16 - {{end}} 17 - {{if and .IsPackageEnabled .CanReadPackages}} 18 - <a class="{{if .IsPackagesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/packages"> 19 - {{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}} 20 - </a> 21 - {{end}} 22 - {{if and .IsRepoIndexerEnabled .CanReadCode}} 23 - <a class="{{if .IsCodePage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code"> 24 - {{svg "octicon-code"}} {{ctx.Locale.Tr "org.code"}} 25 - </a> 26 - {{end}} 27 - {{if .NumMembers}} 23 + {{if and .IsRepoIndexerEnabled .CanReadCode}} 24 + <a class="{{if .IsCodePage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code"> 25 + {{svg "octicon-code"}} {{ctx.Locale.Tr "org.code"}} 26 + </a> 27 + {{end}} 28 + {{if .NumMembers}} 28 29 <a class="{{if $.PageIsOrgMembers}}active {{end}}item" href="{{$.OrgLink}}/members"> 29 30 {{svg "octicon-person"}} {{ctx.Locale.Tr "org.members"}} 30 31 <div class="ui small label">{{.NumMembers}}</div> 31 32 </a> 32 - {{end}} 33 - {{if .IsOrganizationMember}} 33 + {{end}} 34 + {{if .IsOrganizationMember}} 34 35 <a class="{{if $.PageIsOrgTeams}}active {{end}}item" href="{{$.OrgLink}}/teams"> 35 36 {{svg "octicon-people"}} {{ctx.Locale.Tr "org.teams"}} 36 37 {{if .NumTeams}} 37 38 <div class="ui small label">{{.NumTeams}}</div> 38 39 {{end}} 39 40 </a> 40 - {{end}} 41 - 42 - {{if .IsOrganizationOwner}} 43 - <div class="right menu"> 44 - <a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings"> 45 - {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} 46 - </a> 47 - </div> 48 - {{end}} 49 - </div> 41 + {{end}} 42 + {{if .IsOrganizationOwner}} 43 + <a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings"> 44 + {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} 45 + </a> 46 + {{end}} 47 + </div> 48 + </overflow-menu> 50 49 </div>
+2 -2
templates/package/content/alpine.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.alpine.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine"></gitea-origin-url>/$branch/$repository</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code><origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine"></origin-url>/$branch/$repository</code></pre></div> 8 8 <p>{{ctx.Locale.Tr "packages.alpine.registry.info"}}</p> 9 9 </div> 10 10 <div class="field"> 11 11 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alpine.registry.key"}}</label> 12 - <div class="markup"><pre class="code-block"><code>curl -JO <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine/key"></gitea-origin-url></code></pre></div> 12 + <div class="markup"><pre class="code-block"><code>curl -JO <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine/key"></origin-url></code></pre></div> 13 13 </div> 14 14 <div class="field"> 15 15 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alpine.install"}}</label>
+2 -2
templates/package/content/cargo.tmpl
··· 8 8 default = "forgejo" 9 9 10 10 [registries.forgejo] 11 - index = "sparse+<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cargo/"></gitea-origin-url>" # Sparse index 12 - # index = "<gitea-origin-url data-url="{{AppSubUrl}}/{{.PackageDescriptor.Owner.Name}}/_cargo-index.git"></gitea-origin-url>" # Git 11 + index = "sparse+<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cargo/"></origin-url>" # Sparse index 12 + # index = "<origin-url data-url="{{AppSubUrl}}/{{.PackageDescriptor.Owner.Name}}/_cargo-index.git"></origin-url>" # Git 13 13 14 14 [net] 15 15 git-fetch-with-cli = true</code></pre></div>
+1 -1
templates/package/content/chef.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.chef.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>knife[:supermarket_site] = '<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/chef"></gitea-origin-url>'</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>knife[:supermarket_site] = '<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/chef"></origin-url>'</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.chef.install"}}</label>
+1 -1
templates/package/content/composer.tmpl
··· 7 7 <div class="markup"><pre class="code-block"><code>{ 8 8 "repositories": [{ 9 9 "type": "composer", 10 - "url": "<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/composer"></gitea-origin-url>" 10 + "url": "<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/composer"></origin-url>" 11 11 } 12 12 ] 13 13 }</code></pre></div>
+1 -1
templates/package/content/conan.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conan.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>conan remote add gitea <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conan"></gitea-origin-url></code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>conan remote add gitea <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conan"></origin-url></code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conan.install"}}</label>
+3 -3
templates/package/content/conda.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.conda.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>channel_alias: <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url> 7 + <div class="markup"><pre class="code-block"><code>channel_alias: <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url> 8 8 channels: 9 - &#32;&#32;- <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url> 9 + &#32;&#32;- <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url> 10 10 default_channels: 11 - &#32;&#32;- <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url></code></pre></div> 11 + &#32;&#32;- <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url></code></pre></div> 12 12 </div> 13 13 <div class="field"> 14 14 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conda.install"}}</label>
+1 -1
templates/package/content/cran.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.cran.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>options("repos" = c(getOption("repos"), c(forgejo="<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cran"></gitea-origin-url>")))</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>options("repos" = c(getOption("repos"), c(forgejo="<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cran"></origin-url>")))</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.cran.install"}}</label>
+2 -2
templates/package/content/debian.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.debian.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>sudo curl <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key"></gitea-origin-url> -o /etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc 8 - echo "deb [signed-by=/etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc] <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian"></gitea-origin-url> $distribution $component" | sudo tee -a /etc/apt/sources.list.d/forgejo.list 7 + <div class="markup"><pre class="code-block"><code>sudo curl <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key"></origin-url> -o /etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc 8 + echo "deb [signed-by=/etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc] <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian"></origin-url> $distribution $component" | sudo tee -a /etc/apt/sources.list.d/forgejo.list 9 9 sudo apt update</code></pre></div> 10 10 <p>{{ctx.Locale.Tr "packages.debian.registry.info"}}</p> 11 11 </div>
+1 -1
templates/package/content/generic.tmpl
··· 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.generic.download"}}</label> 7 7 <div class="markup"><pre class="code-block"><code> 8 8 {{- range .PackageDescriptor.Files -}} 9 - curl -OJ <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/generic/{{$.PackageDescriptor.Package.Name}}/{{$.PackageDescriptor.Version.Version}}/{{.File.Name}}"></gitea-origin-url> 9 + curl -OJ <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/generic/{{$.PackageDescriptor.Package.Name}}/{{$.PackageDescriptor.Version.Version}}/{{.File.Name}}"></origin-url> 10 10 {{end -}} 11 11 </code></pre></div> 12 12 </div>
+1 -1
templates/package/content/go.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.go.install"}}</label> 7 - <div class="markup"><pre class="code-block"><code>GOPROXY=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/go"></gitea-origin-url> go install {{$.PackageDescriptor.Package.Name}}@{{$.PackageDescriptor.Version.Version}}</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>GOPROXY=<origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/go"></origin-url> go install {{$.PackageDescriptor.Package.Name}}@{{$.PackageDescriptor.Version.Version}}</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{ctx.Locale.Tr "packages.registry.documentation" "Go" "https://forgejo.org/docs/latest/user/packages/go/"}}</label>
+1 -1
templates/package/content/helm.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.helm.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>helm repo add {{AppDomain}} <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/helm"></gitea-origin-url> 7 + <div class="markup"><pre class="code-block"><code>helm repo add {{AppDomain}} <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/helm"></origin-url> 8 8 helm repo update</code></pre></div> 9 9 </div> 10 10 <div class="field">
+4 -4
templates/package/content/maven.tmpl
··· 7 7 <div class="markup"><pre class="code-block"><code>&lt;repositories&gt; 8 8 &lt;repository&gt; 9 9 &lt;id&gt;gitea&lt;/id&gt; 10 - &lt;url&gt;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url>&lt;/url&gt; 10 + &lt;url&gt;<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url>&lt;/url&gt; 11 11 &lt;/repository&gt; 12 12 &lt;/repositories&gt; 13 13 14 14 &lt;distributionManagement&gt; 15 15 &lt;repository&gt; 16 16 &lt;id&gt;gitea&lt;/id&gt; 17 - &lt;url&gt;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url>&lt;/url&gt; 17 + &lt;url&gt;<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url>&lt;/url&gt; 18 18 &lt;/repository&gt; 19 19 20 20 &lt;snapshotRepository&gt; 21 21 &lt;id&gt;gitea&lt;/id&gt; 22 - &lt;url&gt;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url>&lt;/url&gt; 22 + &lt;url&gt;<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url>&lt;/url&gt; 23 23 &lt;/snapshotRepository&gt; 24 24 &lt;/distributionManagement&gt;</code></pre></div> 25 25 </div> ··· 37 37 </div> 38 38 <div class="field"> 39 39 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.maven.download"}}</label> 40 - <div class="markup"><pre class="code-block"><code>mvn dependency:get -DremoteRepositories=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url> -Dartifact={{.PackageDescriptor.Metadata.GroupID}}:{{.PackageDescriptor.Metadata.ArtifactID}}:{{.PackageDescriptor.Version.Version}}</code></pre></div> 40 + <div class="markup"><pre class="code-block"><code>mvn dependency:get -DremoteRepositories=<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url> -Dartifact={{.PackageDescriptor.Metadata.GroupID}}:{{.PackageDescriptor.Metadata.ArtifactID}}:{{.PackageDescriptor.Version.Version}}</code></pre></div> 41 41 </div> 42 42 <div class="field"> 43 43 <label>{{ctx.Locale.Tr "packages.registry.documentation" "Maven" "https://forgejo.org/docs/latest/user/packages/maven/"}}</label>
+1 -1
templates/package/content/npm.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.npm.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>{{if .PackageDescriptor.Metadata.Scope}}{{.PackageDescriptor.Metadata.Scope}}:{{end}}registry=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/npm/"></gitea-origin-url></code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>{{if .PackageDescriptor.Metadata.Scope}}{{.PackageDescriptor.Metadata.Scope}}:{{end}}registry=<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/npm/"></origin-url></code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.npm.install"}}</label>
+1 -1
templates/package/content/nuget.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.nuget.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>dotnet nuget add source --name {{.PackageDescriptor.Owner.Name}} --username your_username --password your_token <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/nuget/index.json"></gitea-origin-url></code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>dotnet nuget add source --name {{.PackageDescriptor.Owner.Name}} --username your_username --password your_token <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/nuget/index.json"></origin-url></code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.nuget.install"}}</label>
+1 -1
templates/package/content/pub.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.pub.install"}}</label> 7 - <div class="markup"><pre class="code-block"><code>dart pub add {{.PackageDescriptor.Package.Name}}:{{.PackageDescriptor.Version.Version}} --hosted-url=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pub/"></gitea-origin-url></code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>dart pub add {{.PackageDescriptor.Package.Name}}:{{.PackageDescriptor.Version.Version}} --hosted-url=<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pub/"></origin-url></code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{ctx.Locale.Tr "packages.registry.documentation" "Pub" "https://forgejo.org/docs/latest/user/packages/pub/"}}</label>
+1 -1
templates/package/content/pypi.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.pypi.install"}}</label> 7 - <div class="markup"><pre class="code-block"><code>pip install --index-url <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></gitea-origin-url> {{.PackageDescriptor.Package.Name}}</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> {{.PackageDescriptor.Package.Name}}</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{ctx.Locale.Tr "packages.registry.documentation" "PyPI" "https://forgejo.org/docs/latest/user/packages/pypi/"}}</label>
+2 -2
templates/package/content/rpm.tmpl
··· 11 11 # {{ctx.Locale.Tr "packages.rpm.distros.redhat"}} 12 12 {{- range $group := .Groups}} 13 13 {{- if $group}}{{$group = print "/" $group}}{{end}} 14 - dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></gitea-origin-url> 14 + dnf config-manager --add-repo <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></origin-url> 15 15 {{- end}} 16 16 17 17 # {{ctx.Locale.Tr "packages.rpm.distros.suse"}} 18 18 {{- range $group := .Groups}} 19 19 {{- if $group}}{{$group = print "/" $group}}{{end}} 20 - zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></gitea-origin-url> 20 + zypper addrepo <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group}}.repo"></origin-url> 21 21 {{- end}}</code></pre></div> 22 22 </div> 23 23 <div class="field">
+2 -2
templates/package/content/rubygems.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rubygems.install"}}:</label> 7 - <div class="markup"><pre class="code-block"><code>gem install {{.PackageDescriptor.Package.Name}} --version &quot;{{.PackageDescriptor.Version.Version}}&quot; --source &quot;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/rubygems"></gitea-origin-url>&quot;</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>gem install {{.PackageDescriptor.Package.Name}} --version &quot;{{.PackageDescriptor.Version.Version}}&quot; --source &quot;<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/rubygems"></origin-url>&quot;</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.rubygems.install2"}}:</label> 11 - <div class="markup"><pre class="code-block"><code>source "<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/rubygems"></gitea-origin-url>" do 11 + <div class="markup"><pre class="code-block"><code>source "<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/rubygems"></origin-url>" do 12 12 gem "{{.PackageDescriptor.Package.Name}}", "{{.PackageDescriptor.Version.Version}}" 13 13 end</code></pre></div> 14 14 </div>
+1 -1
templates/package/content/swift.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.swift.registry"}}</label> 7 - <div class="markup"><pre class="code-block"><code>swift package-registry set <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/swift"></gitea-origin-url></code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>swift package-registry set <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/swift"></origin-url></code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.swift.install"}}</label>
+1 -1
templates/package/content/vagrant.tmpl
··· 4 4 <div class="ui form"> 5 5 <div class="field"> 6 6 <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.vagrant.install"}}</label> 7 - <div class="markup"><pre class="code-block"><code>vagrant box add --box-version {{.PackageDescriptor.Version.Version}} "<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/vagrant/{{.PackageDescriptor.Package.Name}}"></gitea-origin-url>"</code></pre></div> 7 + <div class="markup"><pre class="code-block"><code>vagrant box add --box-version {{.PackageDescriptor.Version.Version}} "<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/vagrant/{{.PackageDescriptor.Package.Name}}"></origin-url>"</code></pre></div> 8 8 </div> 9 9 <div class="field"> 10 10 <label>{{ctx.Locale.Tr "packages.registry.documentation" "Vagrant" "https://forgejo.org/docs/latest/user/packages/vagrant/"}}</label>
+3 -3
templates/repo/diff/image_diff.tmpl
··· 7 7 data-mime-before="{{.sniffedTypeBase.GetMimeType}}" 8 8 data-mime-after="{{.sniffedTypeHead.GetMimeType}}" 9 9 > 10 - <div class="ui secondary pointing tabular top attached borderless menu new-menu"> 11 - <div class="new-menu-inner"> 10 + <overflow-menu class="ui secondary pointing tabular top attached borderless menu"> 11 + <div class="overflow-menu-items tw-justify-center"> 12 12 <a class="item active" data-tab="diff-side-by-side-{{.file.Index}}">{{ctx.Locale.Tr "repo.diff.image.side_by_side"}}</a> 13 13 {{if and .blobBase .blobHead}} 14 14 <a class="item" data-tab="diff-swipe-{{.file.Index}}">{{ctx.Locale.Tr "repo.diff.image.swipe"}}</a> 15 15 <a class="item" data-tab="diff-overlay-{{.file.Index}}">{{ctx.Locale.Tr "repo.diff.image.overlay"}}</a> 16 16 {{end}} 17 17 </div> 18 - </div> 18 + </overflow-menu> 19 19 <div class="image-diff-tabs is-loading"> 20 20 <div class="ui bottom attached tab image-diff-container active" data-tab="diff-side-by-side-{{.file.Index}}"> 21 21 <div class="diff-side-by-side">
+4 -4
templates/repo/header.tmpl
··· 79 79 {{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}} 80 80 </div> 81 81 {{end}} 82 - <div class="ui container secondary pointing tabular top attached borderless menu new-menu navbar"> 82 + <overflow-menu class="ui container secondary pointing tabular top attached borderless menu navbar tw-pt-0 tw-my-0"> 83 83 {{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}} 84 - <div class="new-menu-inner"> 84 + <div class="overflow-menu-items"> 85 85 {{if .Permission.CanRead $.UnitTypeCode}} 86 86 <a class="{{if .PageIsViewCode}}active {{end}}item" href="{{.RepoLink}}{{if and (ne .BranchName .Repository.DefaultBranch) (not $.PageIsWiki)}}/src/{{.BranchNameSubURL}}{{end}}"> 87 87 {{svg "octicon-code"}} {{ctx.Locale.Tr "repo.code"}} ··· 183 183 {{end}} 184 184 </div> 185 185 {{else if .Permission.IsAdmin}} 186 - <div class="new-menu-inner"> 186 + <div class="overflow-menu-items"> 187 187 <a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings"> 188 188 {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} 189 189 </a> 190 190 </div> 191 191 {{end}} 192 - </div> 192 + </overflow-menu> 193 193 <div class="ui tabs divider"></div> 194 194 </div>
+1 -1
templates/repo/issue/view_content/pull_merge_instruction.tmpl
··· 8 8 {{end}} 9 9 <div class="ui secondary segment"> 10 10 {{if eq .PullRequest.Flow 0}} 11 - <div>git fetch -u {{if ne .PullRequest.HeadRepo.ID .PullRequest.BaseRepo.ID}}<gitea-origin-url data-url="{{.PullRequest.HeadRepo.Link}}"></gitea-origin-url>{{else}}origin{{end}} {{.PullRequest.HeadBranch}}:{{$localBranch}}</div> 11 + <div>git fetch -u {{if ne .PullRequest.HeadRepo.ID .PullRequest.BaseRepo.ID}}<origin-url data-url="{{.PullRequest.HeadRepo.Link}}"></origin-url>{{else}}origin{{end}} {{.PullRequest.HeadBranch}}:{{$localBranch}}</div> 12 12 <div>git checkout {{$localBranch}}</div> 13 13 {{else}} 14 14 <div>git fetch -u origin {{.GetGitRefName}}:{{$localBranch}}</div>
+4 -4
templates/repo/view_file.tmpl
··· 144 144 {{end}} 145 145 </tbody> 146 146 </table> 147 - <div class="code-line-menu ui vertical pointing menu tippy-target"> 147 + <div class="code-line-menu tippy-target"> 148 148 {{if $.Permission.CanRead $.UnitTypeIssues}} 149 - <a class="item ref-in-new-issue" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a> 149 + <a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a> 150 150 {{end}} 151 - <a class="item view_git_blame" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a> 152 - <a class="item copy-line-permalink" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a> 151 + <a class="item view_git_blame" role="menuitem" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a> 152 + <a class="item copy-line-permalink" role="menuitem" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a> 153 153 </div> 154 154 {{end}} 155 155 {{end}}
+3 -3
templates/user/auth/signin_navbar.tmpl
··· 1 1 {{if or .EnableOpenIDSignIn .EnableSSPI}} 2 - <div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> 3 - <div class="new-menu-inner"> 2 + <overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar tw-bg-header-wrapper"> 3 + <div class="overflow-menu-items tw-justify-center"> 4 4 <a class="{{if .PageIsLogin}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login"> 5 5 {{ctx.Locale.Tr "auth.tab_signin"}} 6 6 </a> ··· 20 20 </a> 21 21 {{end}} 22 22 </div> 23 - </div> 23 + </overflow-menu> 24 24 {{end}}
+3 -3
templates/user/auth/signup_openid_navbar.tmpl
··· 1 - <div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> 2 - <div class="new-menu-inner"> 1 + <overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar tw-bg-header-wrapper"> 2 + <div class="overflow-menu-items tw-justify-center"> 3 3 <a class="{{if .PageIsOpenIDConnect}}active {{end}}item" href="{{AppSubUrl}}/user/openid/connect"> 4 4 {{ctx.Locale.Tr "auth.openid_connect_title"}} 5 5 </a> ··· 9 9 </a> 10 10 {{end}} 11 11 </div> 12 - </div> 12 + </overflow-menu>
+37 -36
templates/user/overview/header.tmpl
··· 1 - <div class="ui secondary stackable pointing menu"> 2 - {{if and .HasProfileReadme .ContextUser.IsIndividual}} 3 - <a class="{{if eq .TabName "overview"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=overview"> 4 - {{svg "octicon-info"}} {{ctx.Locale.Tr "user.overview"}} 5 - </a> 6 - {{end}} 7 - <a class="{{if eq .TabName "repositories"}}active {{end}} item" href="{{.ContextUser.HomeLink}}?tab=repositories"> 8 - {{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}} 9 - {{if .RepoCount}} 10 - <div class="ui small label">{{.RepoCount}}</div> 1 + <overflow-menu class="ui secondary pointing tabular borderless menu"> 2 + <div class="overflow-menu-items"> 3 + {{if and .HasProfileReadme .ContextUser.IsIndividual}} 4 + <a class="{{if eq .TabName "overview"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=overview"> 5 + {{svg "octicon-info"}} {{ctx.Locale.Tr "user.overview"}} 6 + </a> 11 7 {{end}} 12 - </a> 13 - {{if or .ContextUser.IsIndividual .CanReadProjects}} 14 - <a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item"> 15 - {{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}} 16 - {{if .ProjectCount}} 17 - <div class="ui small label">{{.ProjectCount}}</div> 18 - {{end}} 19 - </a> 20 - {{end}} 21 - {{if and .IsPackageEnabled (or .ContextUser.IsIndividual .CanReadPackages)}} 22 - <a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item"> 23 - {{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}} 8 + <a class="{{if eq .TabName "repositories"}}active {{end}} item" href="{{.ContextUser.HomeLink}}?tab=repositories"> 9 + {{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}} 10 + {{if .RepoCount}} 11 + <div class="ui small label">{{.RepoCount}}</div> 12 + {{end}} 24 13 </a> 25 - {{end}} 26 - {{if and .IsRepoIndexerEnabled (or .ContextUser.IsIndividual .CanReadCode)}} 27 - <a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item"> 28 - {{svg "octicon-code"}} {{ctx.Locale.Tr "user.code"}} 29 - </a> 30 - {{end}} 31 - 32 - {{if .ContextUser.IsIndividual}} 33 - <a class="{{if eq .TabName "activity"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=activity"> 34 - {{svg "octicon-rss"}} {{ctx.Locale.Tr "user.activity"}} 14 + {{if or .ContextUser.IsIndividual .CanReadProjects}} 15 + <a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item"> 16 + {{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}} 17 + {{if .ProjectCount}} 18 + <div class="ui small label">{{.ProjectCount}}</div> 19 + {{end}} 35 20 </a> 36 - {{if not .DisableStars}} 21 + {{end}} 22 + {{if and .IsPackageEnabled (or .ContextUser.IsIndividual .CanReadPackages)}} 23 + <a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item"> 24 + {{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}} 25 + </a> 26 + {{end}} 27 + {{if and .IsRepoIndexerEnabled (or .ContextUser.IsIndividual .CanReadCode)}} 28 + <a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item"> 29 + {{svg "octicon-code"}} {{ctx.Locale.Tr "user.code"}} 30 + </a> 31 + {{end}} 32 + {{if .ContextUser.IsIndividual}} 33 + <a class="{{if eq .TabName "activity"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=activity"> 34 + {{svg "octicon-rss"}} {{ctx.Locale.Tr "user.activity"}} 35 + </a> 36 + {{if not .DisableStars}} 37 37 <a class="{{if eq .TabName "stars"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=stars"> 38 38 {{svg "octicon-star"}} {{ctx.Locale.Tr "user.starred"}} 39 39 {{if .ContextUser.NumStars}} 40 40 <div class="ui small label">{{.ContextUser.NumStars}}</div> 41 41 {{end}} 42 42 </a> 43 - {{else}} 43 + {{else}} 44 44 <a class="{{if eq .TabName "watching"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=watching"> 45 45 {{svg "octicon-eye"}} {{ctx.Locale.Tr "user.watched"}} 46 46 </a> 47 + {{end}} 47 48 {{end}} 48 - {{end}} 49 - </div> 49 + </div> 50 + </overflow-menu>
+7 -62
web_src/css/base.css
··· 259 259 } 260 260 261 261 .page-content .header-wrapper, 262 - .page-content .new-menu { 262 + .page-content overflow-menu { 263 263 margin-top: -15px !important; 264 264 padding-top: 15px !important; 265 265 } ··· 1358 1358 } 1359 1359 } 1360 1360 1361 - .ui.menu.new-menu { 1362 - margin-bottom: 15px; 1363 - background: var(--color-header-wrapper); 1361 + overflow-menu { 1362 + margin-bottom: 15px !important; 1364 1363 border-bottom: 1px solid var(--color-secondary) !important; 1365 - overflow: auto; 1364 + display: flex; 1366 1365 } 1367 1366 1368 - .ui.menu.new-menu .new-menu-inner { 1367 + overflow-menu .overflow-menu-items { 1369 1368 display: flex; 1370 - margin-left: auto; 1371 - margin-right: auto; 1372 - overflow-x: auto; 1373 - width: 100%; 1374 - mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 60px), transparent 100%); 1375 - -webkit-mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 60px), transparent 100%); 1369 + flex: 1; 1376 1370 } 1377 1371 1378 - .ui.menu.new-menu .item { 1372 + overflow-menu .overflow-menu-items .item { 1379 1373 margin-bottom: 0 !important; /* reset fomantic's margin, because the active menu has special bottom border */ 1380 - } 1381 - 1382 - @media (max-width: 767.98px) { 1383 - .ui.menu.new-menu .item { 1384 - width: auto !important; 1385 - } 1386 - } 1387 - 1388 - .ui.menu.new-menu .item:first-child { 1389 - margin-left: auto; /* "justify-content: center" doesn't work with "overflow: auto", so use margin: auto */ 1390 - } 1391 - 1392 - .ui.menu.new-menu .item:last-child { 1393 - padding-right: 30px !important; 1394 - margin-right: auto; 1395 - } 1396 - 1397 - .ui.menu.new-menu::-webkit-scrollbar { 1398 - height: 6px; 1399 - display: none; 1400 - } 1401 - 1402 - .ui.menu.new-menu::-webkit-scrollbar-track { 1403 - background: none !important; 1404 - } 1405 - 1406 - .ui.menu.new-menu::-webkit-scrollbar-thumb { 1407 - box-shadow: none !important; 1408 - } 1409 - 1410 - .ui.menu.new-menu:hover::-webkit-scrollbar { 1411 - display: block; 1412 - } 1413 - 1414 - .repos-search { 1415 - padding-bottom: 0 !important; 1416 - } 1417 - 1418 - .repos-filter { 1419 - margin-top: 0 !important; 1420 - border-bottom-width: 0 !important; 1421 - margin-bottom: 2px !important; 1422 - justify-content: space-evenly; 1423 - } 1424 - 1425 - .ui.secondary.pointing.menu.repos-filter .item { 1426 - padding-left: 4.5px; 1427 - padding-right: 4.5px; 1428 1374 } 1429 1375 1430 1376 .activity-bar-graph { ··· 1932 1878 background: var(--color-body); 1933 1879 border-color: var(--color-secondary); 1934 1880 color: var(--color-text); 1935 - margin-top: 1px; /* offset fomantic's margin-bottom: -1px */ 1936 1881 } 1937 1882 1938 1883 .ui.segment .ui.tabular.menu .active.item,
+28 -1
web_src/css/modules/tippy.css
··· 5 5 display: none !important; 6 6 } 7 7 8 + /* show target element once it's been moved by tippy.js */ 9 + .tippy-content .tippy-target { 10 + display: unset !important; 11 + } 12 + 8 13 [data-tippy-root] { 9 14 max-width: calc(100vw - 32px); 10 15 } ··· 46 51 .tippy-box[data-theme="menu"] { 47 52 background-color: var(--color-menu); 48 53 color: var(--color-text); 54 + box-shadow: 0 6px 18px var(--color-shadow); 49 55 } 50 56 51 57 .tippy-box[data-theme="menu"] .tippy-content { 52 - padding: 0; 58 + padding: 4px 0; 53 59 } 54 60 55 61 .tippy-box[data-theme="menu"] .tippy-svg-arrow-inner { 56 62 fill: var(--color-menu); 57 63 } 58 64 65 + .tippy-box[data-theme="menu"] .item { 66 + display: flex; 67 + align-items: center; 68 + padding: 9px 18px; 69 + color: inherit; 70 + text-decoration: none; 71 + gap: 10px; 72 + } 73 + 74 + .tippy-box[data-theme="menu"] .item:hover { 75 + background: var(--color-hover); 76 + } 77 + 78 + .tippy-box[data-theme="menu"] .item:focus { 79 + background: var(--color-active); 80 + } 81 + 59 82 /* box-with-header theme to look like .ui.attached.segment. can contain .ui.attached.header */ 83 + 84 + .tippy-box[data-theme="box-with-header"] { 85 + box-shadow: 0 6px 18px var(--color-shadow); 86 + } 60 87 61 88 .tippy-box[data-theme="box-with-header"] .tippy-content { 62 89 background: var(--color-box-body);
-10
web_src/css/repo.css
··· 2782 2782 border-left: 1px solid var(--color-secondary); 2783 2783 } 2784 2784 2785 - .repository .ui.menu.new-menu { 2786 - background: none !important; 2787 - } 2788 - 2789 - @media (max-width: 1200px) { 2790 - .repository .ui.menu.new-menu::after { 2791 - background: none !important; 2792 - } 2793 - } 2794 - 2795 2785 .migrate-entries { 2796 2786 display: grid !important; 2797 2787 grid-template-columns: repeat(3, 1fr);
-11
web_src/css/repo/header.css
··· 74 74 background-color: var(--color-header-wrapper); 75 75 } 76 76 77 - .repository .header-wrapper .new-menu { 78 - padding-top: 0 !important; 79 - margin-top: 0 !important; 80 - margin-bottom: 0 !important; 81 - } 82 - 83 - .repository .header-wrapper .new-menu .item { 84 - margin-left: 0 !important; 85 - margin-right: 0 !important; 86 - } 87 - 88 77 @media (max-width: 767.98px) { 89 78 .repo-header .flex-item { 90 79 flex-grow: 1;
-5
web_src/css/repo/linebutton.css
··· 2 2 color: var(--color-text-dark) !important; 3 3 } 4 4 5 - .code-line-menu { 6 - width: auto !important; 7 - border: none !important; /* the border is provided by tippy, not using the `.ui.menu` border */ 8 - } 9 - 10 5 .code-line-button { 11 6 background-color: var(--color-menu); 12 7 color: var(--color-text-light);
-7
web_src/css/shared/repoorg.css
··· 5 5 margin-left: 15px; 6 6 } 7 7 8 - .repository .ui.secondary.stackable.pointing.menu, 9 - .organization .ui.secondary.stackable.pointing.menu { 10 - flex-wrap: wrap; 11 - margin-top: 5px; 12 - margin-bottom: 10px; 13 - } 14 - 15 8 .repository .ui.tabs.container, 16 9 .organization .ui.tabs.container { 17 10 margin-top: 14px;
+40 -22
web_src/js/components/DashboardRepoList.vue
··· 384 384 </div> 385 385 </div> 386 386 </div> 387 - <div class="ui secondary tiny pointing borderless menu center grid repos-filter"> 388 - <a class="item" :class="{active: reposFilter === 'all'}" @click="changeReposFilter('all')"> 389 - {{ textAll }} 390 - <div v-show="reposFilter === 'all'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 391 - </a> 392 - <a class="item" :class="{active: reposFilter === 'sources'}" @click="changeReposFilter('sources')"> 393 - {{ textSources }} 394 - <div v-show="reposFilter === 'sources'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 395 - </a> 396 - <a class="item" :class="{active: reposFilter === 'forks'}" @click="changeReposFilter('forks')"> 397 - {{ textForks }} 398 - <div v-show="reposFilter === 'forks'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 399 - </a> 400 - <a class="item" :class="{active: reposFilter === 'mirrors'}" @click="changeReposFilter('mirrors')" v-if="isMirrorsEnabled"> 401 - {{ textMirrors }} 402 - <div v-show="reposFilter === 'mirrors'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 403 - </a> 404 - <a class="item" :class="{active: reposFilter === 'collaborative'}" @click="changeReposFilter('collaborative')"> 405 - {{ textCollaborative }} 406 - <div v-show="reposFilter === 'collaborative'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 407 - </a> 408 - </div> 387 + <overflow-menu class="ui secondary pointing tabular borderless menu repos-filter"> 388 + <div class="overflow-menu-items tw-justify-center"> 389 + <a class="item" tabindex="0" :class="{active: reposFilter === 'all'}" @click="changeReposFilter('all')"> 390 + {{ textAll }} 391 + <div v-show="reposFilter === 'all'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 392 + </a> 393 + <a class="item" tabindex="0" :class="{active: reposFilter === 'sources'}" @click="changeReposFilter('sources')"> 394 + {{ textSources }} 395 + <div v-show="reposFilter === 'sources'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 396 + </a> 397 + <a class="item" tabindex="0" :class="{active: reposFilter === 'forks'}" @click="changeReposFilter('forks')"> 398 + {{ textForks }} 399 + <div v-show="reposFilter === 'forks'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 400 + </a> 401 + <a class="item" tabindex="0" :class="{active: reposFilter === 'mirrors'}" @click="changeReposFilter('mirrors')" v-if="isMirrorsEnabled"> 402 + {{ textMirrors }} 403 + <div v-show="reposFilter === 'mirrors'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 404 + </a> 405 + <a class="item" tabindex="0" :class="{active: reposFilter === 'collaborative'}" @click="changeReposFilter('collaborative')"> 406 + {{ textCollaborative }} 407 + <div v-show="reposFilter === 'collaborative'" class="ui circular mini grey label">{{ repoTypeCount }}</div> 408 + </a> 409 + </div> 410 + </overflow-menu> 409 411 </div> 410 412 <div v-if="repos.length" class="ui attached table segment gt-rounded-bottom"> 411 413 <ul class="repo-owner-name-list"> ··· 499 501 500 502 ul li:not(:last-child) { 501 503 border-bottom: 1px solid var(--color-secondary); 504 + } 505 + 506 + .repos-search { 507 + padding-bottom: 0 !important; 508 + } 509 + 510 + .repos-filter { 511 + padding-top: 0 !important; 512 + margin-top: 0 !important; 513 + border-bottom-width: 0 !important; 514 + margin-bottom: 2px !important; 515 + } 516 + 517 + .repos-filter .item { 518 + padding-left: 6px !important; 519 + padding-right: 6px !important; 502 520 } 503 521 504 522 .repo-list-link {
+6 -8
web_src/js/modules/tippy.js
··· 7 7 export function createTippy(target, opts = {}) { 8 8 // the callback functions should be destructured from opts, 9 9 // because we should use our own wrapper functions to handle them, do not let the user override them 10 - const {onHide, onShow, onDestroy, ...other} = opts; 10 + const {onHide, onShow, onDestroy, role, theme, ...other} = opts; 11 + 11 12 const instance = tippy(target, { 12 13 appendTo: document.body, 13 14 animation: false, ··· 35 36 return onShow?.(instance); 36 37 }, 37 38 arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, 38 - role: 'menu', // HTML role attribute, only tooltips should use "tooltip" 39 - theme: other.role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header" 39 + role: role || 'menu', // HTML role attribute 40 + theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header" 40 41 plugins: [followCursor], 41 42 ...other, 42 43 }); 43 44 44 - // for popups where content refers to a DOM element, we use the 'tippy-target' class 45 - // to initially hide the content, now we can remove it as the content has been removed 46 - // from the DOM by tippy 47 - if (other.content instanceof Element) { 48 - other.content.classList.remove('tippy-target'); 45 + if (role === 'menu') { 46 + target.setAttribute('aria-haspopup', 'true'); 49 47 } 50 48 51 49 return instance;
+1 -1
web_src/js/webcomponents/GiteaAbsoluteDate.js web_src/js/webcomponents/absolute-date.js
··· 1 - window.customElements.define('gitea-absolute-date', class extends HTMLElement { 1 + window.customElements.define('absolute-date', class extends HTMLElement { 2 2 static observedAttributes = ['date', 'year', 'month', 'weekday', 'day']; 3 3 4 4 update = () => {
+1 -1
web_src/js/webcomponents/GiteaOriginUrl.js web_src/js/webcomponents/origin-url.js
··· 15 15 return urlStr; 16 16 } 17 17 18 - window.customElements.define('gitea-origin-url', class extends HTMLElement { 18 + window.customElements.define('origin-url', class extends HTMLElement { 19 19 connectedCallback() { 20 20 this.textContent = toOriginUrl(this.getAttribute('data-url')); 21 21 }
+1 -1
web_src/js/webcomponents/GiteaOriginUrl.test.js web_src/js/webcomponents/origin-url.test.js
··· 1 - import {toOriginUrl} from './GiteaOriginUrl.js'; 1 + import {toOriginUrl} from './origin-url.js'; 2 2 3 3 test('toOriginUrl', () => { 4 4 const oldLocation = window.location;
+3 -4
web_src/js/webcomponents/README.md
··· 6 6 7 7 # Guidelines 8 8 9 - * These components are loaded in `<head>` (before DOM body), 10 - so they should have their own dependencies and should be very light, 11 - then they won't affect the page loading time too much. 12 - * If the component is not a public one, it's suggested to have its own `Gitea` or `gitea-` prefix to avoid conflicts. 9 + * These components are loaded in `<head>` (before DOM body) in a separate entry point, they need to be lightweight to not affect the page loading time too much. 10 + * Do not import `svg.js` into a web component because that file is currently not tree-shakeable, import svg files individually insteat. 11 + * All our components must be added to `webpack.config.js` so they work correctly in Vue.
+5
web_src/js/webcomponents/index.js
··· 1 + import './polyfills.js'; 2 + import '@github/relative-time-element'; 3 + import './origin-url.js'; 4 + import './overflow-menu.js'; 5 + import './absolute-date.js';
+179
web_src/js/webcomponents/overflow-menu.js
··· 1 + import {throttle} from 'throttle-debounce'; 2 + import {createTippy} from '../modules/tippy.js'; 3 + import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; 4 + import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; 5 + 6 + window.customElements.define('overflow-menu', class extends HTMLElement { 7 + updateItems = throttle(100, () => { 8 + if (!this.tippyContent) { 9 + const div = document.createElement('div'); 10 + div.classList.add('tippy-target'); 11 + div.tabIndex = '-1'; // for initial focus, programmatic focus only 12 + div.addEventListener('keydown', (e) => { 13 + if (e.key === 'Tab') { 14 + const items = this.tippyContent.querySelectorAll('[role="menuitem"]'); 15 + if (e.shiftKey) { 16 + if (document.activeElement === items[0]) { 17 + e.preventDefault(); 18 + items[items.length - 1].focus(); 19 + } 20 + } else { 21 + if (document.activeElement === items[items.length - 1]) { 22 + e.preventDefault(); 23 + items[0].focus(); 24 + } 25 + } 26 + } else if (e.key === 'Escape') { 27 + e.preventDefault(); 28 + e.stopPropagation(); 29 + this.button._tippy.hide(); 30 + this.button.focus(); 31 + } else if (e.key === ' ' || e.code === 'Enter') { 32 + if (document.activeElement?.matches('[role="menuitem"]')) { 33 + e.preventDefault(); 34 + e.stopPropagation(); 35 + document.activeElement.click(); 36 + } 37 + } else if (e.key === 'ArrowDown') { 38 + if (document.activeElement?.matches('.tippy-target')) { 39 + e.preventDefault(); 40 + e.stopPropagation(); 41 + document.activeElement.querySelector('[role="menuitem"]:first-of-type').focus(); 42 + } else if (document.activeElement?.matches('[role="menuitem"]')) { 43 + e.preventDefault(); 44 + e.stopPropagation(); 45 + document.activeElement.nextElementSibling?.focus(); 46 + } 47 + } else if (e.key === 'ArrowUp') { 48 + if (document.activeElement?.matches('.tippy-target')) { 49 + e.preventDefault(); 50 + e.stopPropagation(); 51 + document.activeElement.querySelector('[role="menuitem"]:last-of-type').focus(); 52 + } else if (document.activeElement?.matches('[role="menuitem"]')) { 53 + e.preventDefault(); 54 + e.stopPropagation(); 55 + document.activeElement.previousElementSibling?.focus(); 56 + } 57 + } 58 + }); 59 + this.append(div); 60 + this.tippyContent = div; 61 + } 62 + 63 + // move items in tippy back into the menu items for subsequent measurement 64 + for (const item of this.tippyItems || []) { 65 + this.menuItemsEl.append(item); 66 + } 67 + 68 + // measure which items are partially outside the element and move them into the button menu 69 + this.tippyItems = []; 70 + const menuRight = this.offsetLeft + this.offsetWidth; 71 + const menuItems = this.menuItemsEl.querySelectorAll('.item'); 72 + for (const item of menuItems) { 73 + const itemRight = item.offsetLeft + item.offsetWidth; 74 + if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button 75 + this.tippyItems.push(item); 76 + } 77 + } 78 + 79 + // if there are no overflown items, remove any previously created button 80 + if (!this.tippyItems?.length) { 81 + const btn = this.querySelector('.overflow-menu-button'); 82 + btn?._tippy?.destroy(); 83 + btn?.remove(); 84 + return; 85 + } 86 + 87 + // remove aria role from items that moved from tippy to menu 88 + for (const item of menuItems) { 89 + if (!this.tippyItems.includes(item)) { 90 + item.removeAttribute('role'); 91 + } 92 + } 93 + 94 + // move all items that overflow into tippy 95 + for (const item of this.tippyItems) { 96 + item.setAttribute('role', 'menuitem'); 97 + this.tippyContent.append(item); 98 + } 99 + 100 + // update existing tippy 101 + if (this.button?._tippy) { 102 + this.button._tippy.setContent(this.tippyContent); 103 + return; 104 + } 105 + 106 + // create button initially 107 + const btn = document.createElement('button'); 108 + btn.classList.add('overflow-menu-button', 'btn', 'tw-px-2', 'hover:tw-text-text-dark'); 109 + btn.setAttribute('aria-label', window.config.i18n.more_items); 110 + btn.innerHTML = octiconKebabHorizontal; 111 + this.append(btn); 112 + this.button = btn; 113 + 114 + createTippy(btn, { 115 + trigger: 'click', 116 + hideOnClick: true, 117 + interactive: true, 118 + placement: 'bottom-end', 119 + role: 'menu', 120 + content: this.tippyContent, 121 + onShow: () => { // FIXME: onShown doesn't work (never be called) 122 + setTimeout(() => { 123 + this.tippyContent.focus(); 124 + }, 0); 125 + }, 126 + }); 127 + }); 128 + 129 + init() { 130 + // ResizeObserver triggers on initial render, so we don't manually call `updateItems` here which 131 + // also avoids a full-page FOUC in Firefox that happens when `updateItems` is called too soon. 132 + this.resizeObserver = new ResizeObserver((entries) => { 133 + for (const entry of entries) { 134 + const newWidth = entry.contentBoxSize[0].inlineSize; 135 + if (newWidth !== this.lastWidth) { 136 + requestAnimationFrame(() => { 137 + this.updateItems(); 138 + }); 139 + this.lastWidth = newWidth; 140 + } 141 + } 142 + }); 143 + this.resizeObserver.observe(this); 144 + } 145 + 146 + connectedCallback() { 147 + this.setAttribute('role', 'navigation'); 148 + 149 + // check whether the mandatory `.overflow-menu-items` element is present initially which happens 150 + // with Vue which renders differently than browsers. If it's not there, like in the case of browser 151 + // template rendering, wait for its addition. 152 + // The eslint rule is not sophisticated enough or aware of this problem, see 153 + // https://github.com/43081j/eslint-plugin-wc/pull/130 154 + const menuItemsEl = this.querySelector('.overflow-menu-items'); // eslint-disable-line wc/no-child-traversal-in-connectedcallback 155 + if (menuItemsEl) { 156 + this.menuItemsEl = menuItemsEl; 157 + this.init(); 158 + } else { 159 + this.mutationObserver = new MutationObserver((mutations) => { 160 + for (const mutation of mutations) { 161 + for (const node of mutation.addedNodes) { 162 + if (!isDocumentFragmentOrElementNode(node)) continue; 163 + if (node.classList.contains('overflow-menu-items')) { 164 + this.menuItemsEl = node; 165 + this.mutationObserver?.disconnect(); 166 + this.init(); 167 + } 168 + } 169 + } 170 + }); 171 + this.mutationObserver.observe(this, {childList: true}); 172 + } 173 + } 174 + 175 + disconnectedCallback() { 176 + this.mutationObserver?.disconnect(); 177 + this.resizeObserver?.disconnect(); 178 + } 179 + });
web_src/js/webcomponents/polyfill.js web_src/js/webcomponents/polyfills.js
-6
web_src/js/webcomponents/webcomponents.js
··· 1 - import '@webcomponents/custom-elements'; // polyfill for some browsers like PaleMoon 2 - import './polyfill.js'; 3 - 4 - import '@github/relative-time-element'; 5 - import './GiteaOriginUrl.js'; 6 - import './GiteaAbsoluteDate.js';
+18 -1
webpack.config.js
··· 43 43 sourceMaps = isProduction ? 'reduced' : 'true'; 44 44 } 45 45 46 + // define which web components we use for Vue to not interpret them as Vue components 47 + const webComponents = new Set([ 48 + // our own, in web_src/js/webcomponents 49 + 'overflow-menu', 50 + 'origin-url', 51 + 'absolute-date', 52 + // from dependencies 53 + 'markdown-toolbar', 54 + 'relative-time', 55 + 'text-expander', 56 + ]); 57 + 46 58 const filterCssImport = (url, ...args) => { 47 59 const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import 48 60 const importedFile = url.replace(/[?#].+/, '').toLowerCase(); ··· 72 84 fileURLToPath(new URL('web_src/css/index.css', import.meta.url)), 73 85 ], 74 86 webcomponents: [ 75 - fileURLToPath(new URL('web_src/js/webcomponents/webcomponents.js', import.meta.url)), 87 + fileURLToPath(new URL('web_src/js/webcomponents/index.js', import.meta.url)), 76 88 ], 77 89 forgejoswagger: [ // Forgejo swagger is OpenAPI 3.0.0 and has specific parameters 78 90 fileURLToPath(new URL('web_src/js/standalone/forgejo-swagger.js', import.meta.url)), ··· 125 137 test: /\.vue$/i, 126 138 exclude: /node_modules/, 127 139 loader: 'vue-loader', 140 + options: { 141 + compilerOptions: { 142 + isCustomElement: (tag) => webComponents.has(tag), 143 + }, 144 + }, 128 145 }, 129 146 { 130 147 test: /\.js$/i,