mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-28 16:14:12 +01:00
Merge pull request '[gitea] cherry-pick' (#2397) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2397 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
0533022d63
68 changed files with 810 additions and 350 deletions
4
Makefile
4
Makefile
|
@ -156,8 +156,8 @@ endif
|
|||
FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
|
||||
|
||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
|
||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
|
||||
SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g
|
||||
|
|
|
@ -55,7 +55,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
|
|||
case webhook_module.HookEventPullRequest,
|
||||
webhook_module.HookEventPullRequestSync,
|
||||
webhook_module.HookEventPullRequestAssign,
|
||||
webhook_module.HookEventPullRequestLabel:
|
||||
webhook_module.HookEventPullRequestLabel,
|
||||
webhook_module.HookEventPullRequestReviewRequest,
|
||||
webhook_module.HookEventPullRequestMilestone:
|
||||
return true
|
||||
|
||||
default:
|
||||
|
|
|
@ -186,7 +186,9 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
|||
webhook_module.HookEventPullRequest,
|
||||
webhook_module.HookEventPullRequestSync,
|
||||
webhook_module.HookEventPullRequestAssign,
|
||||
webhook_module.HookEventPullRequestLabel:
|
||||
webhook_module.HookEventPullRequestLabel,
|
||||
webhook_module.HookEventPullRequestReviewRequest,
|
||||
webhook_module.HookEventPullRequestMilestone:
|
||||
return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt)
|
||||
|
||||
case // pull_request_review
|
||||
|
@ -362,13 +364,13 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||
} else {
|
||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
|
||||
// Actions with the same name:
|
||||
// opened, edited, closed, reopened, assigned, unassigned
|
||||
// opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned
|
||||
// Actions need to be converted:
|
||||
// synchronized -> synchronize
|
||||
// label_updated -> labeled
|
||||
// label_cleared -> unlabeled
|
||||
// Unsupported activity types:
|
||||
// converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled
|
||||
// converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued
|
||||
|
||||
action := prPayload.Action
|
||||
switch action {
|
||||
|
|
|
@ -90,6 +90,20 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
|||
}
|
||||
}
|
||||
|
||||
// JSONTemplate renders the template as JSON response
|
||||
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
|
||||
func (ctx *Context) JSONTemplate(tmpl base.TplName) {
|
||||
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
|
||||
if err != nil {
|
||||
ctx.ServerError("unable to find template", err)
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
|
||||
ctx.ServerError("unable to execute template", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RenderToString renders the template content to a string
|
||||
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
|
||||
var buf strings.Builder
|
||||
|
|
|
@ -5,10 +5,7 @@ package context
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
var _ context.Context = TemplateContext(nil)
|
||||
|
@ -36,14 +33,3 @@ func (c TemplateContext) Err() error {
|
|||
func (c TemplateContext) Value(key any) any {
|
||||
return c.parentContext().Value(key)
|
||||
}
|
||||
|
||||
// DataRaceCheck checks whether the template context function "ctx()" returns the consistent context
|
||||
// as the current template's rendering context (request context), to help to find data race issues as early as possible.
|
||||
// When the code is proven to be correct and stable, this function should be removed.
|
||||
func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) {
|
||||
if c.parentContext() != dataCtx {
|
||||
log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2))
|
||||
return "", errors.New("parent context mismatch")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap {
|
|||
"Safe": Safe,
|
||||
"Escape": Escape,
|
||||
"QueryEscape": url.QueryEscape,
|
||||
"JSEscape": template.JSEscapeString,
|
||||
"JSEscape": JSEscapeSafe,
|
||||
"Str2html": Str2html, // TODO: rename it to SanitizeHTML
|
||||
"URLJoin": util.URLJoin,
|
||||
"DotEscape": DotEscape,
|
||||
|
@ -214,6 +214,10 @@ func Escape(s any) template.HTML {
|
|||
panic(fmt.Sprintf("unexpected type %T", s))
|
||||
}
|
||||
|
||||
func JSEscapeSafe(s string) template.HTML {
|
||||
return template.HTML(template.JSEscapeString(s))
|
||||
}
|
||||
|
||||
func RenderEmojiPlain(s any) any {
|
||||
switch v := s.(type) {
|
||||
case string:
|
||||
|
|
|
@ -52,3 +52,7 @@ func TestSubjectBodySeparator(t *testing.T) {
|
|||
"",
|
||||
"Insuficient\n--\nSeparators")
|
||||
}
|
||||
|
||||
func TestJSEscapeSafe(t *testing.T) {
|
||||
assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package i18n
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -82,6 +83,71 @@ c=22
|
|||
assert.Equal(t, "22", lang1.TrString("c"))
|
||||
}
|
||||
|
||||
type stringerPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s *stringerPointerReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type stringerStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s stringerStructReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type errorStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e errorStructReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
type errorPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorPointerReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func TestLocaleWithTemplate(t *testing.T) {
|
||||
ls := NewLocaleStore()
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=<a>%s</a>`), nil))
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
|
||||
tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
|
||||
tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
|
||||
|
||||
cases := []struct {
|
||||
in any
|
||||
want string
|
||||
}{
|
||||
{"<str>", "<a><str></a>"},
|
||||
{[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"},
|
||||
{template.HTML("<html>"), "<a><html></a>"},
|
||||
{stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{<stringerPointerReceiver>}</a>"},
|
||||
{&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a><stringerPointerReceiver ptr></a>"},
|
||||
{stringerStructReceiver{"<stringerStructReceiver>"}, "<a><stringerStructReceiver></a>"},
|
||||
{&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a><stringerStructReceiver ptr></a>"},
|
||||
{errorStructReceiver{"<errorStructReceiver>"}, "<a><errorStructReceiver></a>"},
|
||||
{&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a><errorStructReceiver ptr></a>"},
|
||||
{errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{<errorPointerReceiver>}</a>"},
|
||||
{&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a><errorPointerReceiver ptr></a>"},
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
for _, c := range cases {
|
||||
buf.Reset()
|
||||
assert.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
|
||||
assert.Equal(t, c.want, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleStoreQuirks(t *testing.T) {
|
||||
const nl = "\n"
|
||||
q := func(q1, s string, q2 ...string) string {
|
||||
|
|
|
@ -136,12 +136,14 @@ func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
|
|||
args := slices.Clone(trArgs)
|
||||
for i, v := range args {
|
||||
switch v := v.(type) {
|
||||
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
|
||||
// for most basic types (including template.HTML which is safe), just do nothing and use it
|
||||
case string:
|
||||
args[i] = template.HTML(template.HTMLEscapeString(v))
|
||||
args[i] = template.HTMLEscapeString(v)
|
||||
case fmt.Stringer:
|
||||
args[i] = template.HTMLEscapeString(v.String())
|
||||
default: // int, float, include template.HTML
|
||||
// do nothing, just use it
|
||||
default:
|
||||
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
return template.HTML(l.TrString(trKey, args...))
|
||||
|
|
17
options/license/Brian-Gladman-2-Clause
Normal file
17
options/license/Brian-Gladman-2-Clause
Normal file
|
@ -0,0 +1,17 @@
|
|||
Copyright (C) 1998-2013, Brian Gladman, Worcester, UK. All
|
||||
rights reserved.
|
||||
|
||||
The redistribution and use of this software (with or without
|
||||
changes) is allowed without the payment of fees or royalties
|
||||
provided that:
|
||||
|
||||
source code distributions include the above copyright notice,
|
||||
this list of conditions and the following disclaimer;
|
||||
|
||||
binary distributions include the above copyright notice, this
|
||||
list of conditions and the following disclaimer in their
|
||||
documentation.
|
||||
|
||||
This software is provided 'as is' with no explicit or implied
|
||||
warranties in respect of its operation, including, but not limited
|
||||
to, correctness and fitness for purpose.
|
11
options/license/CMU-Mach-nodoc
Normal file
11
options/license/CMU-Mach-nodoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
Copyright (C) 2002 Naval Research Laboratory (NRL/CCS)
|
||||
|
||||
Permission to use, copy, modify and distribute this software and
|
||||
its documentation is hereby granted, provided that both the
|
||||
copyright notice and this permission notice appear in all copies of
|
||||
the software, derivative works or modified versions, and any
|
||||
portions thereof.
|
||||
|
||||
NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
|
||||
DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM THE USE OF THIS SOFTWARE.
|
1
options/license/GNOME-examples-exception
Normal file
1
options/license/GNOME-examples-exception
Normal file
|
@ -0,0 +1 @@
|
|||
As a special exception, the copyright holders give you permission to copy, modify, and distribute the example code contained in this document under the terms of your choosing, without restriction.
|
16
options/license/Gmsh-exception
Normal file
16
options/license/Gmsh-exception
Normal file
|
@ -0,0 +1,16 @@
|
|||
The copyright holders of Gmsh give you permission to combine Gmsh
|
||||
with code included in the standard release of Netgen (from Joachim
|
||||
Sch"oberl), METIS (from George Karypis at the University of
|
||||
Minnesota), OpenCASCADE (from Open CASCADE S.A.S) and ParaView
|
||||
(from Kitware, Inc.) under their respective licenses. You may copy
|
||||
and distribute such a system following the terms of the GNU GPL for
|
||||
Gmsh and the licenses of the other code concerned, provided that
|
||||
you include the source code of that other code when and as the GNU
|
||||
GPL requires distribution of source code.
|
||||
|
||||
Note that people who make modified versions of Gmsh are not
|
||||
obligated to grant this special exception for their modified
|
||||
versions; it is their choice whether to do so. The GNU General
|
||||
Public License gives permission to release a modified version
|
||||
without this exception; this exception also makes it possible to
|
||||
release a modified version which carries forward this exception.
|
13
options/license/HPND-Fenneberg-Livingston
Normal file
13
options/license/HPND-Fenneberg-Livingston
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose and without fee is hereby granted, provided that this copyright and
|
||||
permission notice appear on all copies and supporting documentation, the
|
||||
name of Lars Fenneberg not be used in advertising or publicity pertaining to
|
||||
distribution of the program without specific prior permission, and notice be
|
||||
given in supporting documentation that copying and distribution is by
|
||||
permission of Lars Fenneberg.
|
||||
|
||||
Lars Fenneberg makes no representations about the suitability of this
|
||||
software for any purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
9
options/license/HPND-INRIA-IMAG
Normal file
9
options/license/HPND-INRIA-IMAG
Normal file
|
@ -0,0 +1,9 @@
|
|||
This software is available with usual "research" terms with
|
||||
the aim of retain credits of the software. Permission to use,
|
||||
copy, modify and distribute this software for any purpose and
|
||||
without fee is hereby granted, provided that the above copyright
|
||||
notice and this permission notice appear in all copies, and
|
||||
the name of INRIA, IMAG, or any contributor not be used in
|
||||
advertising or publicity pertaining to this material without
|
||||
the prior explicit permission. The software is provided "as
|
||||
is" without any warranties, support or liabilities of any kind.
|
25
options/license/Mackerras-3-Clause
Normal file
25
options/license/Mackerras-3-Clause
Normal file
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 1995 Eric Rosenquist. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
3. The name(s) of the authors of this software must not be used to
|
||||
endorse or promote products derived from this software without
|
||||
prior written permission.
|
||||
|
||||
THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
25
options/license/Mackerras-3-Clause-acknowledgment
Normal file
25
options/license/Mackerras-3-Clause-acknowledgment
Normal file
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 1993-2002 Paul Mackerras. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. The name(s) of the authors of this software must not be used to
|
||||
endorse or promote products derived from this software without
|
||||
prior written permission.
|
||||
|
||||
3. Redistributions of any form whatsoever must retain the following
|
||||
acknowledgment:
|
||||
"This product includes software developed by Paul Mackerras
|
||||
<paulus@ozlabs.org>".
|
||||
|
||||
THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
33
options/license/OpenVision
Normal file
33
options/license/OpenVision
Normal file
|
@ -0,0 +1,33 @@
|
|||
Copyright, OpenVision Technologies, Inc., 1993-1996, All Rights
|
||||
Reserved
|
||||
|
||||
WARNING: Retrieving the OpenVision Kerberos Administration system
|
||||
source code, as described below, indicates your acceptance of the
|
||||
following terms. If you do not agree to the following terms, do
|
||||
not retrieve the OpenVision Kerberos administration system.
|
||||
|
||||
You may freely use and distribute the Source Code and Object Code
|
||||
compiled from it, with or without modification, but this Source
|
||||
Code is provided to you "AS IS" EXCLUSIVE OF ANY WARRANTY,
|
||||
INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY OR
|
||||
FITNESS FOR A PARTICULAR PURPOSE, OR ANY OTHER WARRANTY, WHETHER
|
||||
EXPRESS OR IMPLIED. IN NO EVENT WILL OPENVISION HAVE ANY LIABILITY
|
||||
FOR ANY LOST PROFITS, LOSS OF DATA OR COSTS OF PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES, OR FOR ANY SPECIAL, INDIRECT, OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, INCLUDING,
|
||||
WITHOUT LIMITATION, THOSE RESULTING FROM THE USE OF THE SOURCE
|
||||
CODE, OR THE FAILURE OF THE SOURCE CODE TO PERFORM, OR FOR ANY
|
||||
OTHER REASON.
|
||||
|
||||
OpenVision retains all copyrights in the donated Source Code.
|
||||
OpenVision also retains copyright to derivative works of the Source
|
||||
Code, whether created by OpenVision or by a third party. The
|
||||
OpenVision copyright notice must be preserved if derivative works
|
||||
are made based on the donated Source Code.
|
||||
|
||||
OpenVision Technologies, Inc. has donated this Kerberos
|
||||
Administration system to MIT for inclusion in the standard Kerberos
|
||||
5 distribution. This donation underscores our commitment to
|
||||
continuing Kerberos technology development and our gratitude for
|
||||
the valuable work which has been performed by MIT and the Kerberos
|
||||
community.
|
13
options/license/Sun-PPP
Normal file
13
options/license/Sun-PPP
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2001 by Sun Microsystems, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Non-exclusive rights to redistribute, modify, translate, and use
|
||||
this software in source and binary forms, in whole or in part, is
|
||||
hereby granted, provided that the above copyright notice is
|
||||
duplicated in any source form, and that neither the name of the
|
||||
copyright holder nor the author is used to endorse or promote
|
||||
products derived from this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
19
options/license/UMich-Merit
Normal file
19
options/license/UMich-Merit
Normal file
|
@ -0,0 +1,19 @@
|
|||
[C] The Regents of the University of Michigan and Merit Network, Inc. 1992,
|
||||
1993, 1994, 1995 All Rights Reserved
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice appear in all
|
||||
copies of the software and derivative works or modified versions thereof,
|
||||
and that both the copyright notice and this permission and disclaimer
|
||||
notice appear in supporting documentation.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
|
||||
UNIVERSITY OF MICHIGAN AND MERIT NETWORK, INC. DO NOT WARRANT THAT THE
|
||||
FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
|
||||
THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
|
||||
University of Michigan and Merit Network, Inc. shall not be liable for any
|
||||
special, indirect, incidental or consequential damages with respect to any
|
||||
claim by Licensee or any third party arising from use of the software.
|
11
options/license/bcrypt-Solar-Designer
Normal file
11
options/license/bcrypt-Solar-Designer
Normal file
|
@ -0,0 +1,11 @@
|
|||
Written by Solar Designer <solar at openwall.com> in 1998-2014.
|
||||
No copyright is claimed, and the software is hereby placed in the public
|
||||
domain. In case this attempt to disclaim copyright and place the software
|
||||
in the public domain is deemed null and void, then the software is
|
||||
Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
|
||||
general public under the following terms:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted.
|
||||
|
||||
There's ABSOLUTELY NO WARRANTY, express or implied.
|
6
options/license/gtkbook
Normal file
6
options/license/gtkbook
Normal file
|
@ -0,0 +1,6 @@
|
|||
Copyright 2005 Syd Logan, All Rights Reserved
|
||||
|
||||
This code is distributed without warranty. You are free to use
|
||||
this code for any purpose, however, if this code is republished or
|
||||
redistributed in its original form, as hardcopy or electronically,
|
||||
then you must include this copyright notice along with the code.
|
6
options/license/softSurfer
Normal file
6
options/license/softSurfer
Normal file
|
@ -0,0 +1,6 @@
|
|||
Copyright 2001, softSurfer (www.softsurfer.com)
|
||||
This code may be freely used and modified for any purpose
|
||||
providing that this copyright notice is included with it.
|
||||
SoftSurfer makes no warranty for this code, and cannot be held
|
||||
liable for any real or imagined damage resulting from its use.
|
||||
Users of this code must verify correctness for their application.
|
|
@ -2023,15 +2023,10 @@ activity.git_stats_and_deletions = and
|
|||
activity.git_stats_deletion_1 = %d deletion
|
||||
activity.git_stats_deletion_n = %d deletions
|
||||
|
||||
contributors = Contributors
|
||||
contributors.contribution_type.filter_label = Contribution type:
|
||||
contributors.contribution_type.commits = Commits
|
||||
contributors.contribution_type.additions = Additions
|
||||
contributors.contribution_type.deletions = Deletions
|
||||
contributors.loading_title = Loading contributions...
|
||||
contributors.loading_title_failed = Could not load contributions
|
||||
contributors.loading_info = This might take a bit…
|
||||
contributors.component_failed_to_load = An unexpected error happened.
|
||||
|
||||
search = Search
|
||||
search.search_repo = Search repository
|
||||
|
@ -2652,6 +2647,13 @@ error.csv.too_large = Can't render this file because it is too large.
|
|||
error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d.
|
||||
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
|
||||
|
||||
[graphs]
|
||||
component_loading = Loading %s...
|
||||
component_loading_failed = Could not load %s
|
||||
component_loading_info = This might take a bit…
|
||||
component_failed_to_load = An unexpected error happened.
|
||||
contributors.what = contributions
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_full_name_holder = Organization Full Name
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//
|
||||
// Schemes: https, http
|
||||
// BasePath: /api/v1
|
||||
// Version: {{AppVer | JSEscape | Safe}}
|
||||
// Version: {{AppVer | JSEscape}}
|
||||
// License: MIT http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Consumes:
|
||||
|
|
|
@ -579,16 +579,8 @@ func GrantApplicationOAuth(ctx *context.Context) {
|
|||
|
||||
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
|
||||
func OIDCWellKnown(ctx *context.Context) {
|
||||
t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil)
|
||||
if err != nil {
|
||||
ctx.ServerError("unable to find template", err)
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
|
||||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
|
||||
ctx.ServerError("unable to execute template", err)
|
||||
}
|
||||
ctx.JSONTemplate("user/auth/oidc_wellknown")
|
||||
}
|
||||
|
||||
// OIDCKeys generates the JSON Web Key Set
|
||||
|
|
|
@ -18,7 +18,7 @@ const (
|
|||
|
||||
// Contributors render the page to show repository contributors graph
|
||||
func Contributors(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.contributors")
|
||||
ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.contributors")
|
||||
|
||||
ctx.Data["PageIsActivity"] = true
|
||||
ctx.Data["PageIsContributors"] = true
|
||||
|
|
|
@ -662,6 +662,24 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||
}
|
||||
|
||||
if pb != nil && pb.EnableStatusCheck {
|
||||
|
||||
var missingRequiredChecks []string
|
||||
for _, requiredContext := range pb.StatusCheckContexts {
|
||||
contextFound := false
|
||||
matchesRequiredContext := createRequiredContextMatcher(requiredContext)
|
||||
for _, presentStatus := range commitStatuses {
|
||||
if matchesRequiredContext(presentStatus.Context) {
|
||||
contextFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !contextFound {
|
||||
missingRequiredChecks = append(missingRequiredChecks, requiredContext)
|
||||
}
|
||||
}
|
||||
ctx.Data["MissingRequiredChecks"] = missingRequiredChecks
|
||||
|
||||
ctx.Data["is_context_required"] = func(context string) bool {
|
||||
for _, c := range pb.StatusCheckContexts {
|
||||
if c == context {
|
||||
|
@ -730,6 +748,18 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||
return compareInfo
|
||||
}
|
||||
|
||||
func createRequiredContextMatcher(requiredContext string) func(string) bool {
|
||||
if gp, err := glob.Compile(requiredContext); err == nil {
|
||||
return func(contextToCheck string) bool {
|
||||
return gp.Match(contextToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
return func(contextToCheck string) bool {
|
||||
return requiredContext == contextToCheck
|
||||
}
|
||||
}
|
||||
|
||||
type pullCommitList struct {
|
||||
Commits []pull_service.CommitInfo `json:"commits"`
|
||||
LastReviewCommitSha string `json:"last_review_commit_sha"`
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -67,6 +68,88 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model
|
|||
return nil
|
||||
}
|
||||
|
||||
type ReleaseInfo struct {
|
||||
Release *repo_model.Release
|
||||
CommitStatus *git_model.CommitStatus
|
||||
CommitStatuses []*git_model.CommitStatus
|
||||
}
|
||||
|
||||
func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) ([]*ReleaseInfo, error) {
|
||||
releases, err := db.Find[repo_model.Release](ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
release.Repo = ctx.Repo.Repository
|
||||
}
|
||||
|
||||
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Temporary cache commits count of used branches to speed up.
|
||||
countCache := make(map[string]int64)
|
||||
cacheUsers := make(map[int64]*user_model.User)
|
||||
if ctx.Doer != nil {
|
||||
cacheUsers[ctx.Doer.ID] = ctx.Doer
|
||||
}
|
||||
var ok bool
|
||||
|
||||
canReadActions := ctx.Repo.CanRead(unit.TypeActions)
|
||||
|
||||
releaseInfos := make([]*ReleaseInfo, 0, len(releases))
|
||||
for _, r := range releases {
|
||||
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
|
||||
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
r.Publisher = user_model.NewGhostUser()
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cacheUsers[r.PublisherID] = r.Publisher
|
||||
}
|
||||
|
||||
r.Note, err = markdown.RenderString(&markup.RenderContext{
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, r.Note)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.IsDraft {
|
||||
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
info := &ReleaseInfo{
|
||||
Release: r,
|
||||
}
|
||||
|
||||
if canReadActions {
|
||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info.CommitStatus = git_model.CalcCommitStatus(statuses)
|
||||
info.CommitStatuses = statuses
|
||||
}
|
||||
|
||||
releaseInfos = append(releaseInfos, info)
|
||||
}
|
||||
|
||||
return releaseInfos, nil
|
||||
}
|
||||
|
||||
// Releases render releases list page
|
||||
func Releases(ctx *context.Context) {
|
||||
ctx.Data["PageIsReleaseList"] = true
|
||||
|
@ -91,77 +174,21 @@ func Releases(ctx *context.Context) {
|
|||
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
|
||||
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
||||
|
||||
opts := repo_model.FindReleasesOptions{
|
||||
releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
|
||||
ListOptions: listOptions,
|
||||
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
|
||||
IncludeDrafts: writeAccess,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, opts)
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleasesByRepoID", err)
|
||||
ctx.ServerError("getReleaseInfos", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
release.Repo = ctx.Repo.Repository
|
||||
}
|
||||
|
||||
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
|
||||
ctx.ServerError("GetReleaseAttachments", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Temporary cache commits count of used branches to speed up.
|
||||
countCache := make(map[string]int64)
|
||||
cacheUsers := make(map[int64]*user_model.User)
|
||||
if ctx.Doer != nil {
|
||||
cacheUsers[ctx.Doer.ID] = ctx.Doer
|
||||
}
|
||||
var ok bool
|
||||
|
||||
for _, r := range releases {
|
||||
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
|
||||
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
r.Publisher = user_model.NewGhostUser()
|
||||
} else {
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
cacheUsers[r.PublisherID] = r.Publisher
|
||||
}
|
||||
|
||||
r.Note, err = markdown.RenderString(&markup.RenderContext{
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, r.Note)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.IsDraft {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
||||
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Releases"] = releases
|
||||
|
||||
numReleases := ctx.Data["NumReleases"].(int64)
|
||||
pager := context.NewPagination(int(numReleases), opts.PageSize, opts.Page, 5)
|
||||
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
|
@ -249,15 +276,24 @@ func SingleRelease(ctx *context.Context) {
|
|||
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
|
||||
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
||||
|
||||
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*"))
|
||||
releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 1},
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
TagNames: []string{ctx.Params("*")},
|
||||
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
|
||||
IncludeDrafts: writeAccess,
|
||||
})
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound("GetRelease", err)
|
||||
ctx.ServerError("getReleaseInfos", err)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetReleasesByRepoID", err)
|
||||
if len(releases) != 1 {
|
||||
ctx.NotFound("SingleRelease", err)
|
||||
return
|
||||
}
|
||||
|
||||
release := releases[0].Release
|
||||
|
||||
ctx.Data["PageIsSingleTag"] = release.IsTag
|
||||
if release.IsTag {
|
||||
ctx.Data["Title"] = release.TagName
|
||||
|
@ -265,43 +301,7 @@ func SingleRelease(ctx *context.Context) {
|
|||
ctx.Data["Title"] = release.Title
|
||||
}
|
||||
|
||||
release.Repo = ctx.Repo.Repository
|
||||
|
||||
err = repo_model.GetReleaseAttachments(ctx, release)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseAttachments", err)
|
||||
return
|
||||
}
|
||||
|
||||
release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
release.Publisher = user_model.NewGhostUser()
|
||||
} else {
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !release.IsDraft {
|
||||
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
|
||||
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
release.Note, err = markdown.RenderString(&markup.RenderContext{
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, release.Note)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Releases"] = []*repo_model.Release{release}
|
||||
ctx.Data["Releases"] = releases
|
||||
ctx.HTML(http.StatusOK, tplReleasesList)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
|
@ -37,7 +39,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
|||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
|
||||
ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL
|
||||
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
||||
|
||||
// Show OpenID URIs
|
||||
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
|
||||
|
|
|
@ -4,22 +4,10 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
)
|
||||
|
||||
// tplSwaggerV1Json swagger v1 json template
|
||||
const tplSwaggerV1Json base.TplName = "swagger/v1_json"
|
||||
|
||||
// SwaggerV1Json render swagger v1 json
|
||||
func SwaggerV1Json(ctx *context.Context) {
|
||||
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil)
|
||||
if err != nil {
|
||||
ctx.ServerError("unable to find template", err)
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
|
||||
ctx.ServerError("unable to execute template", err)
|
||||
}
|
||||
ctx.JSONTemplate("swagger/v1_json")
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
|||
return fmt.Errorf("head of pull request is missing in event payload")
|
||||
}
|
||||
sha = payload.PullRequest.Head.Sha
|
||||
case webhook_module.HookEventRelease:
|
||||
event = string(run.Event)
|
||||
sha = run.CommitSHA
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -55,6 +55,47 @@ func (n *actionsNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
|
|||
}).Notify(withMethod(ctx, "NewIssue"))
|
||||
}
|
||||
|
||||
// IssueChangeContent notifies change content of issue
|
||||
func (n *actionsNotifier) IssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) {
|
||||
ctx = withMethod(ctx, "IssueChangeContent")
|
||||
|
||||
var err error
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
log.Error("LoadRepo: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
|
||||
if issue.IsPull {
|
||||
if err = issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("loadPullRequest: %v", err)
|
||||
return
|
||||
}
|
||||
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequest).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.PullRequestPayload{
|
||||
Action: api.HookIssueEdited,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}).
|
||||
WithPullRequest(issue.PullRequest).
|
||||
Notify(ctx)
|
||||
return
|
||||
}
|
||||
newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.IssuePayload{
|
||||
Action: api.HookIssueEdited,
|
||||
Index: issue.Index,
|
||||
Issue: convert.ToAPIIssue(ctx, issue),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}).
|
||||
Notify(ctx)
|
||||
}
|
||||
|
||||
// IssueChangeStatus notifies close or reopen issue to notifiers
|
||||
func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, _ *issues_model.Comment, isClosed bool) {
|
||||
ctx = withMethod(ctx, "IssueChangeStatus")
|
||||
|
@ -101,11 +142,40 @@ func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
|
|||
Notify(ctx)
|
||||
}
|
||||
|
||||
// IssueChangeAssignee notifies assigned or unassigned to notifiers
|
||||
func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
|
||||
ctx = withMethod(ctx, "IssueChangeAssignee")
|
||||
|
||||
var action api.HookIssueAction
|
||||
if removed {
|
||||
action = api.HookIssueUnassigned
|
||||
} else {
|
||||
action = api.HookIssueAssigned
|
||||
}
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action)
|
||||
}
|
||||
|
||||
// IssueChangeMilestone notifies assignee to notifiers
|
||||
func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) {
|
||||
ctx = withMethod(ctx, "IssueChangeMilestone")
|
||||
|
||||
var action api.HookIssueAction
|
||||
if issue.MilestoneID > 0 {
|
||||
action = api.HookIssueMilestoned
|
||||
} else {
|
||||
action = api.HookIssueDemilestoned
|
||||
}
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestMilestone, action)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
|
||||
_, _ []*issues_model.Label,
|
||||
) {
|
||||
ctx = withMethod(ctx, "IssueChangeLabels")
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestLabel, api.HookIssueLabelUpdated)
|
||||
}
|
||||
|
||||
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) {
|
||||
var err error
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
log.Error("LoadRepo: %v", err)
|
||||
|
@ -117,20 +187,15 @@ func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
|
|||
return
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
|
||||
if issue.IsPull {
|
||||
if err = issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("loadPullRequest: %v", err)
|
||||
return
|
||||
}
|
||||
if err = issue.PullRequest.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return
|
||||
}
|
||||
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestLabel).
|
||||
newNotifyInputFromIssue(issue, event).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.PullRequestPayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
|
||||
|
@ -140,10 +205,11 @@ func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
|
|||
Notify(ctx)
|
||||
return
|
||||
}
|
||||
newNotifyInputFromIssue(issue, webhook_module.HookEventIssueLabel).
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
|
||||
newNotifyInputFromIssue(issue, event).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
Issue: convert.ToAPIIssue(ctx, issue),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
|
@ -305,6 +371,39 @@ func (n *actionsNotifier) PullRequestReview(ctx context.Context, pr *issues_mode
|
|||
}).Notify(ctx)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
|
||||
if !issue.IsPull {
|
||||
log.Warn("PullRequestReviewRequest: issue is not a pull request: %v", issue.ID)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = withMethod(ctx, "PullRequestReviewRequest")
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
||||
if err := issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("LoadPullRequest failed: %v", err)
|
||||
return
|
||||
}
|
||||
var action api.HookIssueAction
|
||||
if isRequest {
|
||||
action = api.HookIssueReviewRequested
|
||||
} else {
|
||||
action = api.HookIssueReviewRequestRemoved
|
||||
}
|
||||
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestReviewRequest).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.PullRequestPayload{
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}).
|
||||
WithPullRequest(issue.PullRequest).
|
||||
Notify(ctx)
|
||||
}
|
||||
|
||||
func (*actionsNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
||||
ctx = withMethod(ctx, "MergePullRequest")
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func (source *Source) FromDB(bs []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports an SMTPConfig to a serialized format.
|
||||
// ToDB exports the config to a byte slice to be saved into database (this method is just dummy and does nothing for DB source)
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
|
|||
}
|
||||
}
|
||||
|
||||
if matchedCount != len(requiredContexts) {
|
||||
return structs.CommitStatusPending
|
||||
}
|
||||
|
||||
if matchedCount == 0 {
|
||||
status := git_model.CalcCommitStatus(commitStatuses)
|
||||
if status != nil {
|
||||
|
|
|
@ -321,14 +321,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
return nil
|
||||
}
|
||||
|
||||
lowerTags := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
lowerTags = append(lowerTags, strings.ToLower(tag))
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: lowerTags,
|
||||
TagNames: tags,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
|
@ -338,6 +333,11 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
relMap[rel.LowerTagName] = rel
|
||||
}
|
||||
|
||||
lowerTags := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
lowerTags = append(lowerTags, strings.ToLower(tag))
|
||||
}
|
||||
|
||||
newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap))
|
||||
|
||||
emailToUser := make(map[string]*user_model.User)
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{{ctx.Locale.Tr "packages.settings.delete"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ctx.Locale.Tr "packages.settings.delete.notice" `<span class="name"></span>` `<span class="dataVersion"></span>` | Safe}}
|
||||
{{ctx.Locale.Tr "packages.settings.delete.notice" (`<span class="name"></span>`|Safe) (`<span class="dataVersion"></span>`|Safe)}}
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "repo.settings.delete_desc"}}</p>
|
||||
{{ctx.Locale.Tr "repo.settings.delete_notices_2" `<span class="name"></span>` | Safe}}<br>
|
||||
{{ctx.Locale.Tr "repo.settings.delete_notices_2" (`<span class="name"></span>`|Safe)}}<br>
|
||||
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}<br>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
{{ctx.Locale.Tr "admin.monitor.process.cancel"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" `<span class="name"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (`<span class="name"></span>`|Safe)}}</p>
|
||||
<p>{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<div class="inline field {{if .Err_Visibility}}error{{end}}">
|
||||
<span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "settings.visibility"}}</label></span>
|
||||
<div class="ui selection type dropdown">
|
||||
<input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{.visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}">
|
||||
<input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{printf "%d" .visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}">
|
||||
<div class="text">
|
||||
{{if .DefaultUserVisibilityMode.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
|
||||
{{if .DefaultUserVisibilityMode.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
|
||||
|
|
|
@ -16,6 +16,5 @@
|
|||
<script src="{{AssetUrlPrefix}}/js/index.js?v={{AssetVersion}}" onerror="alert('Failed to load asset files from ' + this.src + '. Please make sure the asset files can be accessed.')"></script>
|
||||
|
||||
{{template "custom/footer" .}}
|
||||
{{ctx.DataRaceCheck $.Context}}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
{{template "custom/header" .}}
|
||||
</head>
|
||||
<body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false">
|
||||
{{ctx.DataRaceCheck $.Context}}
|
||||
{{template "custom/body_outer_pre" .}}
|
||||
|
||||
<div class="full height">
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
{{ctx.Locale.Tr "org.members.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
@ -82,7 +82,7 @@
|
|||
{{ctx.Locale.Tr "org.members.remove"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataOrganizationName"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
{{ctx.Locale.Tr "org.members.remove"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataTeamName"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataTeamName"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{{.CsrfTokenHtml}}
|
||||
<div class="field">
|
||||
<label>
|
||||
{{ctx.Locale.Tr "repo.branch.new_branch_from" `<span class="text" id="modal-create-branch-from-span"></span>` | Safe}}
|
||||
{{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|Safe)}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="required field">
|
||||
|
@ -113,7 +113,7 @@
|
|||
<input type="hidden" name="create_tag" value="true">
|
||||
<div class="field">
|
||||
<label>
|
||||
{{ctx.Locale.Tr "repo.tag.create_tag_from" `<span class="text" id="modal-create-tag-from-span"></span>` | Safe}}
|
||||
{{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|Safe)}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="required field">
|
||||
|
@ -184,7 +184,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{{if .Commit.Signature}}
|
||||
<div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw {{$class}}">
|
||||
<div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw gt-mb-0 {{$class}}">
|
||||
<div class="gt-df gt-ac">
|
||||
{{if .Verification.Verified}}
|
||||
{{if ne .Verification.SigningUser.ID 0}}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{{if .Statuses}}
|
||||
{{if and (eq (len .Statuses) 1) .Status.TargetURL}}
|
||||
<a class="gt-vm gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}">
|
||||
<a class="gt-vm {{.AdditionalClasses}} gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}">
|
||||
{{template "repo/commit_status" .Status}}
|
||||
</a>
|
||||
{{else}}
|
||||
<span class="gt-vm" data-tippy="commit-statuses" tabindex="0">
|
||||
<span class="gt-vm {{.AdditionalClasses}}" data-tippy="commit-statuses" tabindex="0">
|
||||
{{template "repo/commit_status" .Status}}
|
||||
</span>
|
||||
{{end}}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}"
|
||||
data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}"
|
||||
data-locale-contribution-type-deletions="{{ctx.Locale.Tr "repo.contributors.contribution_type.deletions"}}"
|
||||
data-locale-loading-title="{{ctx.Locale.Tr "repo.contributors.loading_title"}}"
|
||||
data-locale-loading-title-failed="{{ctx.Locale.Tr "repo.contributors.loading_title_failed"}}"
|
||||
data-locale-loading-info="{{ctx.Locale.Tr "repo.contributors.loading_info"}}"
|
||||
data-locale-component-failed-to-load="{{ctx.Locale.Tr "repo.contributors.component_failed_to_load"}}"
|
||||
data-locale-loading-title="{{ctx.Locale.Tr "graphs.component_loading" (ctx.Locale.Tr "graphs.contributors.what")}}"
|
||||
data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.contributors.what")}}"
|
||||
data-locale-loading-info="{{ctx.Locale.Tr "graphs.component_loading_info"}}"
|
||||
data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}"
|
||||
>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
|
||||
<div>
|
||||
<div class="diff-detail-box diff-box">
|
||||
<div class="gt-df gt-ac gt-fw">
|
||||
<div class="gt-df gt-ac gt-fw gt-gap-3 gt-ml-1">
|
||||
{{if $showFileTree}}
|
||||
<button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}">
|
||||
{{/* the icon meaning is reversed here, "octicon-sidebar-collapse" means show the file tree */}}
|
||||
|
|
|
@ -112,9 +112,9 @@
|
|||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{$link := printf "%s/commit/%s" $.Repository.Link ($.Issue.PullRequest.MergedCommitID|PathEscape)}}
|
||||
{{if eq $.Issue.PullRequest.Status 3}}
|
||||
{{ctx.Locale.Tr "repo.issues.comment_manually_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID)) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape)) $createdStr | Safe}}
|
||||
{{ctx.Locale.Tr "repo.issues.comment_manually_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID) | Safe) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape) | Safe) $createdStr}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.comment_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID)) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape)) $createdStr | Safe}}
|
||||
{{ctx.Locale.Tr "repo.issues.comment_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID) | Safe) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape) | Safe) $createdStr}}
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
{{template "repo/pulls/status" (dict
|
||||
"CommitStatus" .LatestCommitStatus
|
||||
"CommitStatuses" .LatestCommitStatuses
|
||||
"MissingRequiredChecks" .MissingRequiredChecks
|
||||
"ShowHideChecks" true
|
||||
"is_context_required" .is_context_required
|
||||
)}}
|
||||
|
@ -38,7 +39,7 @@
|
|||
{{ctx.Locale.Tr "repo.pulls.merged_success"}}
|
||||
</h3>
|
||||
<div class="merge-section-info">
|
||||
{{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape)) | Str2html}}
|
||||
{{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape) | Safe)}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-section-right">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Template Attributes:
|
||||
* CommitStatus: summary of all commit status state
|
||||
* CommitStatuses: all commit status elements
|
||||
* MissingRequiredChecks: commit check contexts that are required by branch protection but not present
|
||||
* ShowHideChecks: whether use a button to show/hide the checks
|
||||
* is_context_required: Used in pull request commit status check table
|
||||
*/}}
|
||||
|
@ -9,7 +10,7 @@ Template Attributes:
|
|||
{{if .CommitStatus}}
|
||||
<div class="commit-status-panel">
|
||||
<div class="ui top attached header commit-status-header">
|
||||
{{if eq .CommitStatus.State "pending"}}
|
||||
{{if or (eq .CommitStatus.State "pending") (.MissingRequiredChecks)}}
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
|
||||
{{else if eq .CommitStatus.State "success"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}}
|
||||
|
@ -46,6 +47,13 @@ Template Attributes:
|
|||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{range .MissingRequiredChecks}}
|
||||
<div class="commit-status-item">
|
||||
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
|
||||
<div class="status-context gt-ellipsis">{{.}}</div>
|
||||
<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -5,22 +5,24 @@
|
|||
{{template "base/alert" .}}
|
||||
{{template "repo/release_tag_header" .}}
|
||||
<ul id="release-list">
|
||||
{{range $idx, $release := .Releases}}
|
||||
{{range $idx, $info := .Releases}}
|
||||
{{$release := $info.Release}}
|
||||
<li class="ui grid">
|
||||
<div class="ui four wide column meta">
|
||||
<a class="muted" href="{{if not (and .Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}}</a>
|
||||
{{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a>
|
||||
{{template "repo/branch_dropdown" dict "root" $ "release" .}}
|
||||
<a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$release.TagName}}</a>
|
||||
{{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha $release.Sha1}}</a>
|
||||
{{template "repo/branch_dropdown" dict "root" $ "release" $release}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui twelve wide column detail">
|
||||
<div class="gt-df gt-ac gt-sb gt-fw gt-mb-3">
|
||||
<h4 class="release-list-title gt-word-break">
|
||||
<a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a>
|
||||
{{if .IsDraft}}
|
||||
<a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a>
|
||||
{{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "gt-df"}}
|
||||
{{if $release.IsDraft}}
|
||||
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span>
|
||||
{{else if .IsPrerelease}}
|
||||
{{else if $release.IsPrerelease}}
|
||||
<span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span>
|
||||
{{else}}
|
||||
<span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span>
|
||||
|
@ -28,7 +30,7 @@
|
|||
</h4>
|
||||
<div>
|
||||
{{if $.CanCreateRelease}}
|
||||
<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow">
|
||||
<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow">
|
||||
{{svg "octicon-pencil"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
@ -36,11 +38,11 @@
|
|||
</div>
|
||||
<p class="text grey">
|
||||
<span class="author">
|
||||
{{if .OriginalAuthor}}
|
||||
{{svg (MigrationIcon .Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{.OriginalAuthor}}
|
||||
{{else if .Publisher}}
|
||||
{{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}}
|
||||
<a href="{{.Publisher.HomeLink}}">{{.Publisher.GetDisplayName}}</a>
|
||||
{{if $release.OriginalAuthor}}
|
||||
{{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{$release.OriginalAuthor}}
|
||||
{{else if $release.Publisher}}
|
||||
{{ctx.AvatarUtils.Avatar $release.Publisher 20 "gt-mr-2"}}
|
||||
<a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a>
|
||||
{{else}}
|
||||
Ghost
|
||||
{{end}}
|
||||
|
@ -48,15 +50,15 @@
|
|||
<span class="released">
|
||||
{{ctx.Locale.Tr "repo.released_this"}}
|
||||
</span>
|
||||
{{if .CreatedUnix}}
|
||||
<span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span>
|
||||
{{if $release.CreatedUnix}}
|
||||
<span class="time">{{TimeSinceUnix $release.CreatedUnix ctx.Locale}}</span>
|
||||
{{end}}
|
||||
{{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}}</span>
|
||||
{{if and (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span>
|
||||
{{end}}
|
||||
</p>
|
||||
<div class="markup desc">
|
||||
{{Str2html .Note}}
|
||||
{{Str2html $release.Note}}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<details class="download" {{if eq $idx 0}}open{{end}}>
|
||||
|
@ -64,16 +66,15 @@
|
|||
{{ctx.Locale.Tr "repo.release.downloads"}}
|
||||
</summary>
|
||||
<ul class="list">
|
||||
{{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
|
||||
<li>
|
||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
|
||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
|
||||
<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .Attachments}}
|
||||
{{range .Attachments}}
|
||||
{{range $release.Attachments}}
|
||||
<li>
|
||||
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
|
||||
<strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong>
|
||||
|
@ -86,7 +87,6 @@
|
|||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</details>
|
||||
<div class="dot"></div>
|
||||
|
|
|
@ -263,7 +263,7 @@
|
|||
<label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label>
|
||||
<input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}>
|
||||
{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
|
||||
<span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" "<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Str2html}}</span>
|
||||
<span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Safe)}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -31,9 +31,8 @@
|
|||
<li>
|
||||
{{svg "octicon-location"}}
|
||||
<span class="gt-f1">{{.ContextUser.Location}}</span>
|
||||
{{if .UserLocationMapURL}}
|
||||
{{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}}
|
||||
<a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}">
|
||||
{{if .ContextUserLocationMapURL}}
|
||||
<a href="{{.ContextUserLocationMapURL}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}">
|
||||
{{svg "octicon-link-external"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
|
4
templates/swagger/v1_json.tmpl
generated
4
templates/swagger/v1_json.tmpl
generated
|
@ -19,9 +19,9 @@
|
|||
"name": "MIT",
|
||||
"url": "http://opensource.org/licenses/MIT"
|
||||
},
|
||||
"version": "{{AppVer | JSEscape | Safe}}"
|
||||
"version": "{{AppVer | JSEscape}}"
|
||||
},
|
||||
"basePath": "{{AppSubUrl | JSEscape | Safe}}/api/v1",
|
||||
"basePath": "{{AppSubUrl | JSEscape}}/api/v1",
|
||||
"paths": {
|
||||
"/activitypub/user-id/{user-id}": {
|
||||
"get": {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"issuer": "{{AppUrl | JSEscape | Safe}}",
|
||||
"authorization_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/authorize",
|
||||
"token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token",
|
||||
"jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys",
|
||||
"userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo",
|
||||
"introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect",
|
||||
"issuer": "{{AppUrl | JSEscape}}",
|
||||
"authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize",
|
||||
"token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token",
|
||||
"jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys",
|
||||
"userinfo_endpoint": "{{AppUrl | JSEscape}}login/oauth/userinfo",
|
||||
"introspection_endpoint": "{{AppUrl | JSEscape}}login/oauth/introspect",
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"id_token"
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"{{.SigningKey.SigningMethod.Alg | JSEscape | Safe}}"
|
||||
"{{.SigningKey.SigningMethod.Alg | JSEscape}}"
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
{{ctx.Locale.Tr "org.members.leave"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
--border-radius-circle: 50%;
|
||||
--opacity-disabled: 0.55;
|
||||
--height-loading: 16rem;
|
||||
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
|
||||
--tab-size: 4;
|
||||
}
|
||||
|
||||
|
@ -492,6 +493,11 @@ ol.ui.list li,
|
|||
background: var(--color-active) !important;
|
||||
}
|
||||
|
||||
.ui.form textarea:not([rows]) {
|
||||
height: var(--min-height-textarea); /* override fomantic default 12em */
|
||||
min-height: var(--min-height-textarea); /* override fomantic default 8em */
|
||||
}
|
||||
|
||||
/* styles from removed fomantic transition module */
|
||||
.hidden.transition {
|
||||
visibility: hidden;
|
||||
|
|
|
@ -37,13 +37,12 @@
|
|||
.combo-markdown-editor textarea.markdown-text-editor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
max-height: calc(100vh - 200px);
|
||||
max-height: calc(100vh - var(--min-height-textarea));
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.combo-markdown-editor .CodeMirror-scroll {
|
||||
max-height: calc(100vh - 200px);
|
||||
max-height: calc(100vh - var(--min-height-textarea));
|
||||
}
|
||||
|
||||
/* use the same styles as markup/content.css */
|
||||
|
|
|
@ -1498,12 +1498,6 @@
|
|||
background: var(--color-body);
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.repository .diff-detail-box {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.repository .diff-detail-box {
|
||||
flex-wrap: wrap;
|
||||
|
@ -1528,7 +1522,7 @@
|
|||
color: var(--color-red);
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
@media (max-width: 800px) {
|
||||
.repository .diff-detail-box .diff-detail-stats {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -1538,7 +1532,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
flex-wrap: wrap;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
|
@ -1548,15 +1541,6 @@
|
|||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.repository .diff-detail-box .diff-detail-actions {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
.repository .diff-detail-box .diff-detail-actions .ui.button:not(.btn-submit) {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.repository .diff-detail-box span.status {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {POST} from '../../modules/fetch.js';
|
||||
import {imageInfo} from '../../utils/image.js';
|
||||
|
||||
async function uploadFile(file, uploadUrl) {
|
||||
const formData = new FormData();
|
||||
|
@ -109,10 +111,22 @@ const uploadClipboardImage = async (editor, dropzone, e) => {
|
|||
|
||||
const placeholder = `![${name}](uploading ...)`;
|
||||
editor.insertPlaceholder(placeholder);
|
||||
const data = await uploadFile(img, uploadUrl);
|
||||
editor.replacePlaceholder(placeholder, `![${name}](/attachments/${data.uuid})`);
|
||||
|
||||
const $input = $(`<input name="files" type="hidden">`).attr('id', data.uuid).val(data.uuid);
|
||||
const {uuid} = await uploadFile(img, uploadUrl);
|
||||
const {width, dppx} = await imageInfo(img);
|
||||
|
||||
const url = `/attachments/${uuid}`;
|
||||
let text;
|
||||
if (width > 0 && dppx > 1) {
|
||||
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
|
||||
// method to change image size in Markdown that is supported by all implementations.
|
||||
text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
|
||||
} else {
|
||||
text = `![${name}](${url})`;
|
||||
}
|
||||
editor.replacePlaceholder(placeholder, text);
|
||||
|
||||
const $input = $(`<input name="files" type="hidden">`).attr('id', uuid).val(uuid);
|
||||
$files.append($input);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import $ from 'jquery';
|
||||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
import {GET, POST} from '../modules/fetch.js';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoMigrationStatusChecker() {
|
||||
const $repoMigrating = $('#repo_migrating');
|
||||
if (!$repoMigrating.length) return;
|
||||
const repoMigrating = document.getElementById('repo_migrating');
|
||||
if (!repoMigrating) return;
|
||||
|
||||
$('#repo_migrating_retry').on('click', doMigrationRetry);
|
||||
document.getElementById('repo_migrating_retry').addEventListener('click', doMigrationRetry);
|
||||
|
||||
const task = $repoMigrating.attr('data-migrating-task-id');
|
||||
const task = repoMigrating.getAttribute('data-migrating-task-id');
|
||||
|
||||
// returns true if the refresh still need to be called after a while
|
||||
// returns true if the refresh still needs to be called after a while
|
||||
const refresh = async () => {
|
||||
const res = await GET(`${appSubUrl}/user/task/${task}`);
|
||||
if (res.status !== 200) return true; // continue to refresh if network error occurs
|
||||
|
@ -21,7 +20,7 @@ export function initRepoMigrationStatusChecker() {
|
|||
|
||||
// for all status
|
||||
if (data.message) {
|
||||
$('#repo_migrating_progress_message').text(data.message);
|
||||
document.getElementById('repo_migrating_progress_message').textContent = data.message;
|
||||
}
|
||||
|
||||
// TaskStatusFinished
|
||||
|
@ -37,7 +36,7 @@ export function initRepoMigrationStatusChecker() {
|
|||
showElem('#repo_migrating_retry');
|
||||
showElem('#repo_migrating_failed');
|
||||
showElem('#repo_migrating_failed_image');
|
||||
$('#repo_migrating_failed_error').text(data.message);
|
||||
document.getElementById('repo_migrating_failed_error').textContent = data.message;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -59,6 +58,6 @@ export function initRepoMigrationStatusChecker() {
|
|||
}
|
||||
|
||||
async function doMigrationRetry(e) {
|
||||
await POST($(e.target).attr('data-migrating-task-retry-url'));
|
||||
await POST(e.target.getAttribute('data-migrating-task-retry-url'));
|
||||
window.location.reload();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import $ from 'jquery';
|
||||
import {checkAppUrl} from './common-global.js';
|
||||
|
||||
export function initUserAuthOauth2() {
|
||||
|
@ -21,30 +20,3 @@ export function initUserAuthOauth2() {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initUserAuthLinkAccountView() {
|
||||
const $lnkUserPage = $('.page-content.user.link-account');
|
||||
if ($lnkUserPage.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const $signinTab = $lnkUserPage.find('.item[data-tab="auth-link-signin-tab"]');
|
||||
const $signUpTab = $lnkUserPage.find('.item[data-tab="auth-link-signup-tab"]');
|
||||
const $signInView = $lnkUserPage.find('.tab[data-tab="auth-link-signin-tab"]');
|
||||
const $signUpView = $lnkUserPage.find('.tab[data-tab="auth-link-signup-tab"]');
|
||||
|
||||
$signUpTab.on('click', () => {
|
||||
$signinTab.removeClass('active');
|
||||
$signInView.removeClass('active');
|
||||
$signUpTab.addClass('active');
|
||||
$signUpView.addClass('active');
|
||||
return false;
|
||||
});
|
||||
|
||||
$signinTab.on('click', () => {
|
||||
$signUpTab.removeClass('active');
|
||||
$signUpView.removeClass('active');
|
||||
$signinTab.addClass('active');
|
||||
$signInView.addClass('active');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import {initFindFileInRepo} from './features/repo-findfile.js';
|
|||
import {initCommentContent, initMarkupContent} from './markup/content.js';
|
||||
import {initPdfViewer} from './render/pdf.js';
|
||||
|
||||
import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js';
|
||||
import {initUserAuthOauth2} from './features/user-auth.js';
|
||||
import {
|
||||
initRepoIssueDue,
|
||||
initRepoIssueReferenceRepositorySearch,
|
||||
|
@ -178,7 +178,6 @@ onDomReady(() => {
|
|||
initCommitStatuses();
|
||||
initCaptcha();
|
||||
|
||||
initUserAuthLinkAccountView();
|
||||
initUserAuthOauth2();
|
||||
initUserAuthWebAuthn();
|
||||
initUserAuthWebAuthnRegister();
|
||||
|
|
47
web_src/js/utils/image.js
Normal file
47
web_src/js/utils/image.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
export async function pngChunks(blob) {
|
||||
const uint8arr = new Uint8Array(await blob.arrayBuffer());
|
||||
const chunks = [];
|
||||
if (uint8arr.length < 12) return chunks;
|
||||
const view = new DataView(uint8arr.buffer);
|
||||
if (view.getBigUint64(0) !== 9894494448401390090n) return chunks;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let index = 8;
|
||||
while (index < uint8arr.length) {
|
||||
const len = view.getUint32(index);
|
||||
chunks.push({
|
||||
name: decoder.decode(uint8arr.slice(index + 4, index + 8)),
|
||||
data: uint8arr.slice(index + 8, index + 8 + len),
|
||||
});
|
||||
index += len + 12;
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// decode a image and try to obtain width and dppx. If will never throw but instead
|
||||
// return default values.
|
||||
export async function imageInfo(blob) {
|
||||
let width = 0; // 0 means no width could be determined
|
||||
let dppx = 1; // 1 dot per pixel for non-HiDPI screens
|
||||
|
||||
if (blob.type === 'image/png') { // only png is supported currently
|
||||
try {
|
||||
for (const {name, data} of await pngChunks(blob)) {
|
||||
const view = new DataView(data.buffer);
|
||||
if (name === 'IHDR' && data?.length) {
|
||||
// extract width from mandatory IHDR chunk
|
||||
width = view.getUint32(0);
|
||||
} else if (name === 'pHYs' && data?.length) {
|
||||
// extract dppx from optional pHYs chunk, assuming pixels are square
|
||||
const unit = view.getUint8(8);
|
||||
if (unit === 1) {
|
||||
dppx = Math.round(view.getUint32(0) / 39.3701) / 72; // meter to inch to dppx
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return {width, dppx};
|
||||
}
|
29
web_src/js/utils/image.test.js
Normal file
29
web_src/js/utils/image.test.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import {pngChunks, imageInfo} from './image.js';
|
||||
|
||||
const pngNoPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAADUlEQVQIHQECAP3/AAAAAgABzePRKwAAAABJRU5ErkJggg==';
|
||||
const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A==';
|
||||
const pngEmpty = 'data:image/png;base64,';
|
||||
|
||||
async function dataUriToBlob(datauri) {
|
||||
return await (await globalThis.fetch(datauri)).blob();
|
||||
}
|
||||
|
||||
test('pngChunks', async () => {
|
||||
expect(await pngChunks(await dataUriToBlob(pngNoPhys))).toEqual([
|
||||
{name: 'IHDR', data: new Uint8Array([0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0])},
|
||||
{name: 'IDAT', data: new Uint8Array([8, 29, 1, 2, 0, 253, 255, 0, 0, 0, 2, 0, 1])},
|
||||
{name: 'IEND', data: new Uint8Array([])},
|
||||
]);
|
||||
expect(await pngChunks(await dataUriToBlob(pngPhys))).toEqual([
|
||||
{name: 'IHDR', data: new Uint8Array([0, 0, 0, 2, 0, 0, 0, 2, 8, 2, 0, 0, 0])},
|
||||
{name: 'pHYs', data: new Uint8Array([0, 0, 22, 37, 0, 0, 22, 37, 1])},
|
||||
{name: 'IDAT', data: new Uint8Array([8, 215, 99, 144, 53, 151, 0, 34, 6, 8, 5, 0, 11, 242, 1, 177])},
|
||||
]);
|
||||
expect(await pngChunks(await dataUriToBlob(pngEmpty))).toEqual([]);
|
||||
});
|
||||
|
||||
test('imageInfo', async () => {
|
||||
expect(await imageInfo(await dataUriToBlob(pngNoPhys))).toEqual({width: 1, dppx: 1});
|
||||
expect(await imageInfo(await dataUriToBlob(pngPhys))).toEqual({width: 2, dppx: 2});
|
||||
expect(await imageInfo(await dataUriToBlob(pngEmpty))).toEqual({width: 0, dppx: 1});
|
||||
});
|
Loading…
Reference in a new issue