Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fbf5e968d9 | |||
| 5f10865ab7 | |||
| 8774100d25 | |||
| de55903c71 | |||
| 416278c747 | |||
| 58cf5dd410 | |||
| 0ab62c2b95 | |||
| 97b534f66b | |||
| eb969b8f82 | |||
| b310e8ed18 | |||
| e8286de3be | |||
| 4648a021c5 | |||
| 1a335e741b | |||
| 998337e80d | |||
| 009566a3ce | |||
| 2ca88587d4 | |||
| 05f8df8a9e | |||
| 44b6c62093 | |||
| 4557edfe98 | |||
| 3b8cfc0c03 | |||
| 55568524a7 | |||
| 62f4a3ce37 | |||
| 3c8405a3b2 | |||
| e105e047a4 | |||
| f9f2d45c13 | |||
| 3a62a5d8c1 | |||
| f2f4367dbe | |||
| 7b4e85a473 | |||
| d8904e2846 | |||
| 4fabef6a65 | |||
| f26bd3e273 | |||
| c5daac3366 | |||
| 916211004d | |||
| 02fdc1a194 | |||
| 1b0bba09b9 | |||
| 0c0d1c1493 | |||
| 9461599b57 | |||
| 414560f470 | |||
| b43345986a | |||
| 7fbbd26b20 | |||
| b26bf4bfe8 |
@@ -16,7 +16,7 @@ env:
|
||||
GOPRIVATE: git.marketally.com
|
||||
GONOSUMDB: git.marketally.com
|
||||
GOTOOLCHAIN: local
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.25.9"
|
||||
NODE_VERSION: "22"
|
||||
|
||||
jobs:
|
||||
@@ -24,6 +24,8 @@ jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: linux-latest
|
||||
env:
|
||||
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-lint
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -51,7 +53,8 @@ jobs:
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: make deps-frontend deps-backend
|
||||
run: |
|
||||
make deps-frontend deps-backend
|
||||
|
||||
- name: Run Go linter
|
||||
run: make lint-go
|
||||
@@ -64,6 +67,8 @@ jobs:
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
runs-on: linux-latest
|
||||
env:
|
||||
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-unit
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -83,7 +88,8 @@ jobs:
|
||||
cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
run: |
|
||||
go mod download
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
@@ -99,6 +105,8 @@ jobs:
|
||||
test-pgsql:
|
||||
name: Integration Tests (PostgreSQL)
|
||||
runs-on: linux-latest
|
||||
env:
|
||||
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-pgsql
|
||||
services:
|
||||
pgsql:
|
||||
image: postgres:15
|
||||
@@ -140,7 +148,8 @@ jobs:
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: make deps-frontend deps-backend
|
||||
run: |
|
||||
make deps-frontend deps-backend
|
||||
|
||||
- name: Build frontend
|
||||
run: make frontend
|
||||
@@ -351,7 +360,7 @@ jobs:
|
||||
sed -i "s|replace git.marketally.com/gitcaddy/gitcaddy-vault => ../gitcaddy-vault|replace git.marketally.com/gitcaddy/gitcaddy-vault => git.marketally.com/gitcaddy/gitcaddy-vault $VAULT_VERSION|" go.mod
|
||||
fi
|
||||
cat go.mod | grep -A2 "gitcaddy-vault" || true
|
||||
go mod tidy -v 2>&1
|
||||
go mod download
|
||||
|
||||
- name: Update vault dependency (Windows)
|
||||
if: matrix.goos == 'windows'
|
||||
@@ -366,7 +375,7 @@ jobs:
|
||||
$content = $content -replace 'replace git.marketally.com/gitcaddy/gitcaddy-vault => ../gitcaddy-vault', "replace git.marketally.com/gitcaddy/gitcaddy-vault => git.marketally.com/gitcaddy/gitcaddy-vault $vaultVersion"
|
||||
Set-Content go.mod $content -NoNewline
|
||||
Get-Content go.mod | Select-String "gitcaddy/vault" -Context 0,2
|
||||
go mod tidy
|
||||
go mod download
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -378,7 +387,8 @@ jobs:
|
||||
|
||||
- name: Install dependencies (Unix)
|
||||
if: matrix.goos != 'windows'
|
||||
run: make deps-frontend deps-backend
|
||||
run: |
|
||||
make deps-frontend deps-backend
|
||||
|
||||
- name: Install dependencies (Windows)
|
||||
if: matrix.goos == 'windows'
|
||||
@@ -627,7 +637,7 @@ jobs:
|
||||
echo "Building with vault $VAULT_VERSION"
|
||||
sed -i "s|replace git.marketally.com/gitcaddy/gitcaddy-vault => ../gitcaddy-vault|replace git.marketally.com/gitcaddy/gitcaddy-vault => git.marketally.com/gitcaddy/gitcaddy-vault $VAULT_VERSION|" go.mod
|
||||
cat go.mod | grep -A2 "gitcaddy-vault" || true
|
||||
/usr/local/go/bin/go mod tidy -v 2>&1
|
||||
/usr/local/go/bin/go mod download
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
12
go.mod
12
go.mod
@@ -2,7 +2,7 @@ module code.gitcaddy.com/server/v3
|
||||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
toolchain go1.25.9
|
||||
|
||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||
// But some CAs use negative serial number, just relax the check. related:
|
||||
@@ -16,7 +16,7 @@ require (
|
||||
connectrpc.com/connect v1.18.1
|
||||
|
||||
// GitCaddy Vault plugin (compiled-in)
|
||||
git.marketally.com/gitcaddy/gitcaddy-vault v1.0.60
|
||||
git.marketally.com/gitcaddy/gitcaddy-vault v1.0.61
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
@@ -326,6 +326,9 @@ replace github.com/go-ini/ini => github.com/go-ini/ini v1.66.6
|
||||
// Use GitCaddy fork with capability support
|
||||
replace code.gitea.io/actions-proto-go => git.marketally.com/gitcaddy/actions-proto-go v0.5.8
|
||||
|
||||
// Mirror of deleted github.com/hexops/gotextdiff
|
||||
replace github.com/hexops/gotextdiff => git.marketally.com/mirrors/gotextdiff v1.0.3
|
||||
|
||||
// Vault plugin - use local directory for development
|
||||
replace git.marketally.com/gitcaddy/gitcaddy-vault => ../gitcaddy-vault
|
||||
|
||||
@@ -337,4 +340,9 @@ exclude github.com/goccy/go-json v0.4.11
|
||||
|
||||
exclude github.com/satori/go.uuid v1.2.0
|
||||
|
||||
// Pin SDK to v0.22.0 — v0.24.x requires go 1.26
|
||||
exclude code.gitea.io/sdk/gitea v0.24.0
|
||||
|
||||
exclude code.gitea.io/sdk/gitea v0.24.1
|
||||
|
||||
tool code.gitea.io/gitea-vet
|
||||
|
||||
6
go.sum
6
go.sum
@@ -31,6 +31,10 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.marketally.com/gitcaddy/actions-proto-go v0.5.8 h1:MBipeHvY6A0jcobvziUtzgatZTrV4fs/HE1rPQxREN4=
|
||||
git.marketally.com/gitcaddy/actions-proto-go v0.5.8/go.mod h1:RPu21UoRD3zSAujoZR6LJwuVNa2uFRBveadslczCRfQ=
|
||||
git.marketally.com/gitcaddy/gitcaddy-vault v1.0.60 h1:SHmAZG1PxbC9kG9gWdrhrQ7cHVmP0e6iMloBK100kQA=
|
||||
git.marketally.com/gitcaddy/gitcaddy-vault v1.0.60/go.mod h1:UAmVrJUXHsGe3iJz2gmow2zsIE/KAXaW4jKytrxT8jQ=
|
||||
git.marketally.com/mirrors/gotextdiff v1.0.3 h1:Mxf+YurdCHT4y1GNiZCTDWYtVXSxhlLUeG7g7i9Za70=
|
||||
git.marketally.com/mirrors/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 h1:ohdxegvslDEllZmRNDqpKun6L4Oq81jNdEDtGgHEV2c=
|
||||
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||
@@ -490,8 +494,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
|
||||
@@ -444,6 +444,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(367, "Add pages translation table for multi-language support", v1_26.AddPagesTranslationTable),
|
||||
newMigration(368, "Add owner_display_name to repository", v1_26.AddOwnerDisplayNameToRepository),
|
||||
newMigration(369, "Add public_release_downloads to repository", v1_26.AddPublicReleaseDownloadsToRepository),
|
||||
newMigration(370, "Add exclude_hidden_files to push_mirror", v1_26.AddExcludeHiddenFilesToPushMirror),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
16
models/migrations/v1_26/v370.go
Normal file
16
models/migrations/v1_26/v370.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddExcludeHiddenFilesToPushMirror(x *xorm.Engine) error {
|
||||
type PushMirror struct {
|
||||
ExcludeHiddenFiles bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(PushMirror))
|
||||
}
|
||||
@@ -24,11 +24,12 @@ type PushMirror struct {
|
||||
RemoteName string
|
||||
RemoteAddress string `xorm:"VARCHAR(2048)"`
|
||||
|
||||
SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
|
||||
Interval time.Duration
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
|
||||
LastError string `xorm:"text"`
|
||||
SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
|
||||
ExcludeHiddenFiles bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Interval time.Duration
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
|
||||
LastError string `xorm:"text"`
|
||||
}
|
||||
|
||||
type PushMirrorOptions struct {
|
||||
@@ -86,6 +87,11 @@ func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdatePushMirrorCols updates the specified columns of the push mirror.
|
||||
func UpdatePushMirrorCols(ctx context.Context, m *PushMirror, cols ...string) (int64, error) {
|
||||
return db.GetEngine(ctx).ID(m.ID).Cols(cols...).Update(m)
|
||||
}
|
||||
|
||||
func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
|
||||
if opts.RepoID > 0 {
|
||||
_, err := db.Delete[PushMirror](ctx, opts)
|
||||
|
||||
156
modules/git/tree_filter.go
Normal file
156
modules/git/tree_filter.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitcaddy.com/server/v3/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
// FilterTreeOptions controls which paths are excluded when building a filtered tree.
|
||||
type FilterTreeOptions struct {
|
||||
// ExcludeDotFiles excludes all entries whose name starts with "."
|
||||
ExcludeDotFiles bool
|
||||
// ExcludePaths is a set of specific paths to exclude (exact top-level names)
|
||||
ExcludePaths map[string]bool
|
||||
}
|
||||
|
||||
// ShouldExclude returns true if the given entry name should be filtered out.
|
||||
func (opts *FilterTreeOptions) ShouldExclude(name string) bool {
|
||||
if opts.ExcludeDotFiles && strings.HasPrefix(name, ".") {
|
||||
return true
|
||||
}
|
||||
if opts.ExcludePaths != nil && opts.ExcludePaths[name] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasFilters returns true if any filtering is configured.
|
||||
func (opts *FilterTreeOptions) HasFilters() bool {
|
||||
return opts.ExcludeDotFiles || len(opts.ExcludePaths) > 0
|
||||
}
|
||||
|
||||
// BuildFilteredCommit creates a new commit that has the same content as the
|
||||
// given commit but with excluded paths removed from the top-level tree.
|
||||
// It uses git plumbing (ls-tree, mktree, commit-tree) and creates no refs —
|
||||
// only loose objects that git gc will collect naturally.
|
||||
// Returns the SHA of the new commit, or the original SHA if nothing was filtered.
|
||||
func (repo *Repository) BuildFilteredCommit(commitSHA string, opts *FilterTreeOptions) (string, error) {
|
||||
if !opts.HasFilters() {
|
||||
return commitSHA, nil
|
||||
}
|
||||
|
||||
// 1. Get the top-level tree entries for this commit
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
err := gitcmd.NewCommand("ls-tree").AddArguments("--").AddDynamicArguments(commitSHA).
|
||||
WithDir(repo.Path).
|
||||
WithStdout(stdout).
|
||||
WithStderr(stderr).
|
||||
Run(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ls-tree %s: %s: %w", commitSHA, stderr.String(), err)
|
||||
}
|
||||
|
||||
// 2. Filter entries
|
||||
var filteredLines []string
|
||||
excluded := false
|
||||
for line := range strings.SplitSeq(strings.TrimRight(stdout.String(), "\n"), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// ls-tree output format: "<mode> <type> <sha>\t<name>"
|
||||
tabIdx := strings.IndexByte(line, '\t')
|
||||
if tabIdx < 0 {
|
||||
continue
|
||||
}
|
||||
name := line[tabIdx+1:]
|
||||
if opts.ShouldExclude(name) {
|
||||
excluded = true
|
||||
continue
|
||||
}
|
||||
filteredLines = append(filteredLines, line)
|
||||
}
|
||||
|
||||
// Nothing was filtered — return original commit
|
||||
if !excluded {
|
||||
return commitSHA, nil
|
||||
}
|
||||
|
||||
// 3. Create a new tree from filtered entries via mktree
|
||||
treeInput := strings.Join(filteredLines, "\n") + "\n"
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
err = gitcmd.NewCommand("mktree").
|
||||
WithDir(repo.Path).
|
||||
WithStdin(strings.NewReader(treeInput)).
|
||||
WithStdout(stdout).
|
||||
WithStderr(stderr).
|
||||
Run(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("mktree: %s: %w", stderr.String(), err)
|
||||
}
|
||||
newTreeSHA := strings.TrimSpace(stdout.String())
|
||||
|
||||
// 4. Get the original commit's parent(s) and message
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
err = gitcmd.NewCommand("log", "-1", "--format=%P%n%B").AddArguments("--").AddDynamicArguments(commitSHA).
|
||||
WithDir(repo.Path).
|
||||
WithStdout(stdout).
|
||||
WithStderr(stderr).
|
||||
Run(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("log %s: %s: %w", commitSHA, stderr.String(), err)
|
||||
}
|
||||
|
||||
logOutput := stdout.String()
|
||||
lines := strings.SplitN(logOutput, "\n", 2)
|
||||
parentLine := ""
|
||||
message := "filtered commit"
|
||||
if len(lines) >= 1 {
|
||||
parentLine = strings.TrimSpace(lines[0])
|
||||
}
|
||||
if len(lines) >= 2 {
|
||||
message = strings.TrimRight(lines[1], "\n")
|
||||
}
|
||||
|
||||
// 5. Create a new commit referencing the filtered tree
|
||||
cmd := gitcmd.NewCommand("commit-tree").AddDynamicArguments(newTreeSHA)
|
||||
for parent := range strings.FieldsSeq(parentLine) {
|
||||
cmd = cmd.AddArguments("-p").AddDynamicArguments(parent)
|
||||
}
|
||||
|
||||
messageReader := strings.NewReader(message + "\n")
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
err = cmd.
|
||||
WithDir(repo.Path).
|
||||
WithStdin(messageReader).
|
||||
WithStdout(stdout).
|
||||
WithStderr(stderr).
|
||||
Run(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("commit-tree: %s: %w", stderr.String(), err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// UpdateRef updates a ref to point at the given SHA.
|
||||
func (repo *Repository) UpdateRef(ref, sha string) error {
|
||||
stderr := new(bytes.Buffer)
|
||||
err := gitcmd.NewCommand("update-ref").AddDynamicArguments(ref, sha).
|
||||
WithDir(repo.Path).
|
||||
WithStderr(stderr).
|
||||
Run(repo.Ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update-ref %s %s: %s: %w", ref, sha, stderr.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -36,6 +36,12 @@ func License(name string) ([]byte, error) {
|
||||
return AssetFS().ReadFile("license", name)
|
||||
}
|
||||
|
||||
// LicenseBundleFile reads a single file from a license bundle directory
|
||||
// (e.g. options/license-bundles/CSL-1.0/GOVERNANCE.md).
|
||||
func LicenseBundleFile(bundleName, fileName string) ([]byte, error) {
|
||||
return AssetFS().ReadFile("license-bundles", bundleName, fileName)
|
||||
}
|
||||
|
||||
// Labels reads the content of a specific labels from static/bindata or custom path.
|
||||
func Labels(name string) ([]byte, error) {
|
||||
return AssetFS().ReadFile("label", name)
|
||||
|
||||
@@ -30,10 +30,14 @@ type LandingConfig struct {
|
||||
Stats []StatConfig `yaml:"stats,omitempty" json:"stats,omitempty"`
|
||||
|
||||
// Value propositions
|
||||
ValueProps []ValuePropConfig `yaml:"value_props,omitempty" json:"value_props,omitempty"`
|
||||
ValueProps []ValuePropConfig `yaml:"value_props,omitempty" json:"value_props,omitempty"`
|
||||
ValuePropsHeadline string `yaml:"value_props_headline,omitempty" json:"value_props_headline,omitempty"`
|
||||
ValuePropsSubheadline string `yaml:"value_props_subheadline,omitempty" json:"value_props_subheadline,omitempty"`
|
||||
|
||||
// Features
|
||||
Features []FeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
|
||||
Features []FeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
|
||||
FeaturesHeadline string `yaml:"features_headline,omitempty" json:"features_headline,omitempty"`
|
||||
FeaturesSubheadline string `yaml:"features_subheadline,omitempty" json:"features_subheadline,omitempty"`
|
||||
|
||||
// Social proof
|
||||
SocialProof SocialProofConfig `yaml:"social_proof,omitempty" json:"social_proof,omitzero"`
|
||||
@@ -53,6 +57,9 @@ type LandingConfig struct {
|
||||
// Comparison section
|
||||
Comparison ComparisonSectionConfig `yaml:"comparison,omitempty" json:"comparison,omitzero"`
|
||||
|
||||
// Cross-promote section
|
||||
CrossPromote CrossPromoteSectionConfig `yaml:"cross_promote,omitempty" json:"cross_promote,omitzero"`
|
||||
|
||||
// Navigation visibility
|
||||
Navigation NavigationConfig `yaml:"navigation,omitempty" json:"navigation,omitzero"`
|
||||
|
||||
@@ -251,6 +258,13 @@ type ComparisonFeatureConfig struct {
|
||||
Values []string `yaml:"values,omitempty" json:"values,omitempty"` // "true"/"false" for check/x, anything else displayed as text
|
||||
}
|
||||
|
||||
// CrossPromoteSectionConfig controls the cross-promote section on the landing page
|
||||
type CrossPromoteSectionConfig struct {
|
||||
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
||||
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
||||
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
||||
}
|
||||
|
||||
// NavigationConfig controls which built-in navigation links appear in the header and footer
|
||||
type NavigationConfig struct {
|
||||
ShowDocs bool `yaml:"show_docs,omitempty" json:"show_docs,omitempty"`
|
||||
@@ -259,16 +273,17 @@ type NavigationConfig struct {
|
||||
ShowReleases bool `yaml:"show_releases,omitempty" json:"show_releases,omitempty"`
|
||||
ShowIssues bool `yaml:"show_issues,omitempty" json:"show_issues,omitempty"`
|
||||
// Translatable labels for nav items and section headers (defaults to English)
|
||||
LabelValueProps string `yaml:"label_value_props,omitempty" json:"label_value_props,omitempty"`
|
||||
LabelFeatures string `yaml:"label_features,omitempty" json:"label_features,omitempty"`
|
||||
LabelPricing string `yaml:"label_pricing,omitempty" json:"label_pricing,omitempty"`
|
||||
LabelBlog string `yaml:"label_blog,omitempty" json:"label_blog,omitempty"`
|
||||
LabelGallery string `yaml:"label_gallery,omitempty" json:"label_gallery,omitempty"`
|
||||
LabelCompare string `yaml:"label_compare,omitempty" json:"label_compare,omitempty"`
|
||||
LabelDocs string `yaml:"label_docs,omitempty" json:"label_docs,omitempty"`
|
||||
LabelReleases string `yaml:"label_releases,omitempty" json:"label_releases,omitempty"`
|
||||
LabelAPI string `yaml:"label_api,omitempty" json:"label_api,omitempty"`
|
||||
LabelIssues string `yaml:"label_issues,omitempty" json:"label_issues,omitempty"`
|
||||
LabelValueProps string `yaml:"label_value_props,omitempty" json:"label_value_props,omitempty"`
|
||||
LabelFeatures string `yaml:"label_features,omitempty" json:"label_features,omitempty"`
|
||||
LabelPricing string `yaml:"label_pricing,omitempty" json:"label_pricing,omitempty"`
|
||||
LabelBlog string `yaml:"label_blog,omitempty" json:"label_blog,omitempty"`
|
||||
LabelGallery string `yaml:"label_gallery,omitempty" json:"label_gallery,omitempty"`
|
||||
LabelCompare string `yaml:"label_compare,omitempty" json:"label_compare,omitempty"`
|
||||
LabelCrossPromote string `yaml:"label_cross_promote,omitempty" json:"label_cross_promote,omitempty"`
|
||||
LabelDocs string `yaml:"label_docs,omitempty" json:"label_docs,omitempty"`
|
||||
LabelReleases string `yaml:"label_releases,omitempty" json:"label_releases,omitempty"`
|
||||
LabelAPI string `yaml:"label_api,omitempty" json:"label_api,omitempty"`
|
||||
LabelIssues string `yaml:"label_issues,omitempty" json:"label_issues,omitempty"`
|
||||
}
|
||||
|
||||
// FooterConfig represents footer settings
|
||||
@@ -477,58 +492,64 @@ func TemplateDefaultLabels(template string) NavigationConfig {
|
||||
switch template {
|
||||
case "architecture-deep-dive":
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Systems Analysis",
|
||||
LabelFeatures: "Technical Specifications",
|
||||
LabelPricing: "Resource Allocation",
|
||||
LabelBlog: "Dispatches",
|
||||
LabelGallery: "Visual Index",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Systems Analysis",
|
||||
LabelFeatures: "Technical Specifications",
|
||||
LabelPricing: "Resource Allocation",
|
||||
LabelBlog: "Dispatches",
|
||||
LabelGallery: "Visual Index",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
case "bold-marketing":
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Why choose this",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Investment",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Why choose this",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Investment",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
case "minimalist-docs":
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Why choose this",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Investment",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Why choose this",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Investment",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
case "open-source-hero":
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Why choose us",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Why choose us",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
case "saas-conversion":
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Why",
|
||||
LabelFeatures: "Features",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Why",
|
||||
LabelFeatures: "Features",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
default:
|
||||
// developer-tool, documentation-first, visual-showcase, cli-terminal
|
||||
return NavigationConfig{
|
||||
LabelValueProps: "Why choose us",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelValueProps: "Why choose us",
|
||||
LabelFeatures: "Capabilities",
|
||||
LabelPricing: "Pricing",
|
||||
LabelBlog: "Blog",
|
||||
LabelGallery: "Gallery",
|
||||
LabelCompare: "Compare",
|
||||
LabelCrossPromote: "Related Offerings",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,3 +191,25 @@ type OrgProfileContent struct {
|
||||
Readme string `json:"readme,omitempty"`
|
||||
HasCSS bool `json:"has_css"`
|
||||
}
|
||||
|
||||
// OrgRecentActivity represents a recently updated repo in the org overview
|
||||
type OrgRecentActivity struct {
|
||||
RepoName string `json:"repo_name"`
|
||||
RepoFullName string `json:"repo_full_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
CommitMessage string `json:"commit_message,omitempty"`
|
||||
CommitTime int64 `json:"commit_time,omitempty"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
}
|
||||
|
||||
// OrgOverviewV2 represents the enhanced organization overview for v2 API
|
||||
type OrgOverviewV2 struct {
|
||||
Organization *Organization `json:"organization"`
|
||||
PinnedRepos []*OrgPinnedRepo `json:"pinned_repos"`
|
||||
PinnedGroups []*OrgPinnedGroup `json:"pinned_groups"`
|
||||
PublicMembers []*OrgPublicMember `json:"public_members"`
|
||||
TotalMembers int64 `json:"total_members"`
|
||||
Stats *OrgOverviewStats `json:"stats"`
|
||||
Profile *OrgProfileContent `json:"profile,omitempty"`
|
||||
RecentActivity []OrgRecentActivity `json:"recent_activity,omitempty"`
|
||||
}
|
||||
|
||||
85
options/license-bundles/CSL-1.0/CONTRIBUTING.md
Normal file
85
options/license-bundles/CSL-1.0/CONTRIBUTING.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Community Specification Contribution Policy 1.0
|
||||
|
||||
This document provides the contribution policy for specifications and other documents developed using the Community Specification process in a repository (each a “Working Group”). Additional or alternate contribution policies may be adopted and documented by the Working Group.
|
||||
|
||||
## 1. Contribution Guidelines.
|
||||
|
||||
This Working Group accepts contributions via pull requests. The following section outlines the process for merging contributions to the specification
|
||||
|
||||
**1.1. Issues.** Issues are used as the primary method for tracking anything to do with this specification Working Group.
|
||||
|
||||
**1.1.1. Issue Types.** There are three types of issues (each with their own corresponding label):
|
||||
|
||||
**1.1.1.1. Discussion.** These are support or functionality inquiries that we want to have a record of for future reference. Depending on the discussion, these can turn into "Spec Change" issues.
|
||||
|
||||
**1.1.1.2. Proposal.** Used for items that propose a new ideas or functionality that require a larger discussion. This allows for feedback from others before a specification change is actually written. All issues that are proposals should both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become a "Spec Change" and does not require a milestone.
|
||||
|
||||
**1.1.1.3. Spec Change:** These track specific spec changes and ideas until they are complete. They can evolve from "Proposal" and "Discussion" items, or can be submitted individually depending on the size. Each spec change should be placed into a milestone.
|
||||
|
||||
## 2. Issue Lifecycle.
|
||||
|
||||
The issue lifecycle is mainly driven by the Maintainer. All issue types follow the same general lifecycle. Differences are noted below.
|
||||
|
||||
**2.1. Issue Creation.**
|
||||
|
||||
**2.2. Triage.**
|
||||
|
||||
o The Editor in charge of triaging will apply the proper labels for the issue. This includes labels for priority, type, and metadata.
|
||||
|
||||
o (If needed) Clean up the title to succinctly and clearly state the issue. Also ensure that proposals are prefaced with "Proposal".
|
||||
|
||||
**2.3. Discussion.**
|
||||
|
||||
o "Spec Change" issues should be connected to the pull request that resolves it.
|
||||
|
||||
o Whoever is working on a "Spec Change" issue should either assign the issue to themselves or make a comment in the issue saying that they are taking it.
|
||||
|
||||
o "Proposal" and "Discussion" issues should stay open until resolved.
|
||||
|
||||
**2.4. Issue Closure.**
|
||||
|
||||
## 3. How to Contribute a Patch.
|
||||
|
||||
The Working Group uses pull requests to track changes. To submit a change to the specification:
|
||||
|
||||
**3.1 Fork the Repo, modify the Specification to Address the Issue.**
|
||||
|
||||
**3.2. Submit a Pull Request.**
|
||||
|
||||
## 4. Pull Request Workflow.
|
||||
|
||||
The next section contains more information on the workflow followed for Pull Requests.
|
||||
|
||||
**4.1. Pull Request Creation.**
|
||||
|
||||
o We welcome pull requests that are currently in progress. They are a great way to keep track of important work that is in-flight, but useful for others to see. If a pull request is a work in progress, it should be prefaced with "WIP: [title]". You should also add the wip label Once the pull request is ready for review, remove "WIP" from the title and label.
|
||||
|
||||
o It is preferred, but not required, to have a pull request tied to a specific issue. There can be circumstances where if it is a quick fix then an issue might be overkill. The details provided in the pull request description would suffice in this case.
|
||||
|
||||
**4.2. Triage**
|
||||
|
||||
o The Editor in charge of triaging will apply the proper labels for the issue. This should include at least a size label, a milestone, and awaiting review once all labels are applied.
|
||||
|
||||
**4.3. Reviewing/Discussion.**
|
||||
|
||||
o All reviews will be completed using the review tool.
|
||||
|
||||
o A "Comment" review should be used when there are questions about the spec that should be answered, but that don't involve spec changes. This type of review does not count as approval.
|
||||
|
||||
o A "Changes Requested" review indicates that changes to the spec need to be made before they will be merged.
|
||||
|
||||
o Reviewers should update labels as needed (such as needs rebase).
|
||||
|
||||
o When a review is approved, the reviewer should add LGTM as a comment.
|
||||
|
||||
o Final approval is required by a designated Editor. Merging is blocked without this final approval. Editors will factor reviews from all other reviewers into their approval process.
|
||||
|
||||
**4.4. Responsive.** Pull request owner should try to be responsive to comments by answering questions or changing text. Once all comments have been addressed, the pull request is ready to be merged.
|
||||
|
||||
**4.5. Merge or Close.**
|
||||
|
||||
o A pull request should stay open until a Maintainer has marked the pull request as approved.
|
||||
|
||||
o Pull requests can be closed by the author without merging.
|
||||
|
||||
o Pull requests may be closed by a Maintainer if the decision is made that it is not going to be merged.
|
||||
51
options/license-bundles/CSL-1.0/GOVERNANCE.md
Normal file
51
options/license-bundles/CSL-1.0/GOVERNANCE.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Community Specification Governance Policy 1.0
|
||||
|
||||
This document provides the governance policy for specifications and other documents developed using the Community Specification process in a repository (each a “Working Group”). Each Working Group and must adhere to the requirements in this document.
|
||||
|
||||
## 1. Roles.
|
||||
|
||||
Each Working Group may include the following roles. Additional roles may be adopted and documented by the Working Group.
|
||||
|
||||
**1.1. Maintainer.** “Maintainers” are responsible for organizing activities around developing, maintaining, and updating the specification(s) developed by the Working Group. Maintainers are also responsible for determining consensus and coordinating appeals. Each Working Group will designate one or more Maintainer for that Working Group. A Working Group may select a new or additional Maintainer(s) upon Approval of the Working Group Participants.
|
||||
|
||||
**1.2. Editor.** “Editors” are responsible for ensuring that the contents of the document accurately reflect the decisions that have been made by the group, and that the specification adheres to formatting and content guidelines. Each Working Group will designate an Editor for that Working Group. A Working Group may select a new Editor upon Approval of the Working Group Participants.
|
||||
|
||||
**1.3. Participants.** “Participants” are those that have made Contributions to the Working Group subject to the Community Specification License.
|
||||
|
||||
## 2. Decision Making.
|
||||
|
||||
**2.1. Consensus-Based Decision Making.** Working Groups make decisions through a consensus process (“Approval” or “Approved”). While the agreement of all Participants is preferred, it is not required for consensus. Rather, the Maintainer will determine consensus based on their good faith consideration of a number of factors, including the dominant view of the Working Group Participants and nature of support and objections. The Maintainer will document evidence of consensus in accordance with these requirements.
|
||||
|
||||
**2.2. Appeal Process.** Decisions may be appealed be via a pull request or an issue, and that appeal will be considered by the Maintainer in good faith, who will respond in writing within a reasonable time.
|
||||
|
||||
## 3. Ways of Working.
|
||||
|
||||
Inspired by [ANSI’s Essential Requirements for Due Process](https://share.ansi.org/Shared%20Documents/Standards%20Activities/American%20National%20Standards/Procedures,%20Guides,%20and%20Forms/2020_ANSI_Essential_Requirements.pdf), Community Specification Working Groups must adhere to consensus-based due process requirements. These requirements apply to activities related to the development of consensus for approval, revision, reaffirmation, and withdrawal of Community Specifications. Due process means that any person (organization, company, government agency, individual, etc.) with a direct and material interest has a right to participate by: a) expressing a position and its basis, b) having that position considered, and c) having the right to appeal. Due process allows for equity and fair play. The following constitute the minimum acceptable due process requirements for the development of consensus.
|
||||
|
||||
**3.1. Openness.** Participation shall be open to all persons who are directly and materially affected by the activity in question. There shall be no undue financial barriers to participation. Voting membership on the consensus body shall not be conditional upon membership in any organization, nor unreasonably restricted on the basis of technical qualifications or other such requirements. Membership in a Working Group’s parent organization, if any, may be required.
|
||||
|
||||
**3.2. Lack of Dominance.** The development process shall not be dominated by any single interest category, individual or organization. Dominance means a position or exercise of dominant authority, leadership, or influence by reason of superior leverage, strength, or representation to the exclusion of fair and equitable consideration of other viewpoints.
|
||||
|
||||
**3.3. Balance.** The development process should have a balance of interests. Participants from diverse interest categories shall be sought with the objective of achieving balance.
|
||||
|
||||
**3.4. Coordination and Harmonization.** Good faith efforts shall be made to resolve potential conflicts between and among deliverables developed under this Working Group and existing industry standards.
|
||||
|
||||
**3.5. Consideration of Views and Objections.** Prompt consideration shall be given to the written views and objections of all Participants.
|
||||
|
||||
**3.6. Written procedures.** This governance document and other materials documenting the Community Specification development process shall be available to any interested person.
|
||||
|
||||
## 4. Specification Development Process.
|
||||
|
||||
**4.1. Pre-Draft.** Any Participant may submit a proposed initial draft document as a candidate Draft Specification of that Working Group. The Maintainer will designate each submission as a “Pre-Draft” document.
|
||||
|
||||
**4.2. Draft.** Each Pre-Draft document of a Working Group must first be Approved to become a” Draft Specification”. Once the Working Group approves a document as a Draft Specification, the Draft Specification becomes the basis for all going forward work on that specification.
|
||||
|
||||
**4.3. Working Group Approval.** Once a Working Group believes it has achieved the objectives for its specification as described in the Scope, it will Approve that Draft Specification and progress it to “Approved Specification” status.
|
||||
|
||||
**4.4. Publication and Submission.** Upon the designation of a Draft Specification as an Approved Specification, the Maintainer will publish the Approved Specification in a manner agreed upon by the Working Group Participants (i.e., Working Group Participant only location, publicly available location, Working Group maintained website, Working Group member website, etc.). The publication of an Approved Specification in a publicly accessible manner must include the terms under which the Approved Specification is being made available under.
|
||||
|
||||
**4.5. Submissions to Standards Bodies.** No Draft Specification or Approved Specification may be submitted to another standards development organization without Working group Approval. Upon reaching Approval, the Maintainer will coordinate the submission of the applicable Draft Specification or Approved Specification to another standards development organization. Working Group Participants that developed that Draft Specification or Approved Specification agree to grant the copyright rights necessary to make those submissions.
|
||||
|
||||
## 5. Non-Confidential, Restricted Disclosure.
|
||||
|
||||
Information disclosed in connection with any Working Group activity, including but not limited to meetings, Contributions, and submissions, is not confidential, regardless of any markings or statements to the contrary. Notwithstanding the foregoing, if the Working Group is collaborating via a private repository, the Participants will not make any public disclosures of that information contained in that private repository without the Approval of the Working Group.
|
||||
99
options/license-bundles/CSL-1.0/LICENSE.md
Normal file
99
options/license-bundles/CSL-1.0/LICENSE.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Community Specification License 1.0
|
||||
|
||||
**The Purpose of this License.** This License sets forth the terms under which 1) Contributor will participate in and contribute to the development of specifications, standards, best practices, guidelines, and other similar materials under this Working Group, and 2) how the materials developed under this License may be used. It is not intended for source code. Capitalized terms are defined in the License’s last section.
|
||||
|
||||
**1. Copyright.**
|
||||
|
||||
**1.1. Copyright License.** Contributor grants everyone a non-sublicensable, perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as expressly stated in this License) copyright license, without any obligation for accounting, to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute any materials it submits to the full extent of its copyright interest in those materials. Contributor also acknowledges that the Working Group may exercise copyright rights in the Specification, including the rights to submit the Specification to another standards organization.
|
||||
|
||||
**1.2. Copyright Attribution.** As a condition, anyone exercising this copyright license must include attribution to the Working Group in any derivative work based on materials developed by the Working Group. That attribution must include, at minimum, the material’s name, version number, and source from where the materials were retrieved. Attribution is not required for implementations of the Specification.
|
||||
|
||||
**2. Patents.**
|
||||
|
||||
**2.1. Patent License.**
|
||||
|
||||
**2.1.1. As a Result of Contributions.**
|
||||
|
||||
**2.1.1.1. As a Result of Contributions to Draft Specifications.** Contributor grants Licensee a non-sublicensable, perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as expressly stated in this License) license to its Necessary Claims in 1) Contributor’s Contributions and 2) to the Draft Specification that is within Scope as of the date of that Contribution, in both cases for Licensee’s Implementation of the Draft Specification, except for those patent claims excluded by Contributor under Section 3.
|
||||
|
||||
**2.1.1.2. For Approved Specifications.** Contributor grants Licensee a non-sublicensable, perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as expressly stated in this License) license to its Necessary Claims included in the Approved Specification that are within Scope for Licensee’s Implementation of the Approved Specification, except for those patent claims excluded by Contributor under Section 3.
|
||||
|
||||
**2.1.2. Patent Grant from Licensee.** Licensee grants each other Licensee a non-sublicensable, perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as expressly stated in this License) license to its Necessary Claims for its Implementation, except for those patent claims excluded under Section 3.
|
||||
|
||||
**2.1.3. Licensee Acceptance.** The patent grants set forth in Section 2.1 extend only to Licensees that have indicated their agreement to this License as follows:
|
||||
|
||||
**2.1.3.1. Source Code Distributions.** For distribution in source code, by including this License in the root directory of the source code with the Implementation;
|
||||
|
||||
**2.1.3.2. Non-Source Code Distributions.** For distribution in any form other than source code, by including this License in the documentation, legal notices, via notice in the software, and/or other written materials provided with the Implementation; or
|
||||
|
||||
**2.1.3.3. Via Notices.md.** By issuing pull request or commit to the Specification’s repository’s Notices.md file by the Implementer’s authorized representative, including the Implementer’s name, authorized individual and system identifier, and Specification version.
|
||||
|
||||
**2.1.4. Defensive Termination.** If any Licensee files or maintains a claim in a court asserting that a Necessary Claim is infringed by an Implementation, any licenses granted under this License to the Licensee are immediately terminated unless 1) that claim is directly in response to a claim against Licensee regarding an Implementation, or 2) that claim was brought to enforce the terms of this License, including intervention in a third-party action by a Licensee.
|
||||
|
||||
**2.1.5. Additional Conditions.** This License is not an assurance (i) that any of Contributor’s copyrights or issued patent claims cover an Implementation of the Specification or are enforceable or (ii) that an Implementation of the Specification would not infringe intellectual property rights of any third party.
|
||||
|
||||
**2.2. Patent Licensing Commitment.** In addition to the rights granted in Section 2.1, Contributor agrees to grant everyone a no charge, royalty-free license on reasonable and non-discriminatory terms to Contributor’s Necessary Claims that are within Scope for:
|
||||
1) Implementations of a Draft Specification, where such license applies only to those Necessary Claims infringed by implementing Contributor's Contribution(s) included in that Draft Specification, and
|
||||
2) Implementations of the Approved Specification.
|
||||
|
||||
This patent licensing commitment does not apply to those claims subject to Contributor’s Exclusion Notice under Section 3.
|
||||
|
||||
**2.3. Effect of Withdrawal.** Contributor may withdraw from the Working Group by issuing a pull request or commit providing notice of withdrawal to the Working Group repository’s Notices.md file. All of Contributor’s existing commitments and obligations with respect to the Working Group up to the date of that withdrawal notice will remain in effect, but no new obligations will be incurred.
|
||||
|
||||
**2.4. Binding Encumbrance.** This License is binding on any future owner, assignee, or party who has been given the right to enforce any Necessary Claims against third parties.
|
||||
|
||||
**3. Patent Exclusion.**
|
||||
|
||||
**3.1. As a Result of Contributions.** Contributor may exclude Necessary Claims from its licensing commitments incurred under Section 2.1.1 by issuing an Exclusion Notice within 45 days of the date of that Contribution. Contributor may not issue an Exclusion Notice for any material that has been included in a Draft Deliverable for more than 45 days prior to the date of that Contribution.
|
||||
|
||||
**3.2. As a Result of a Draft Specification Becoming an Approved Specification.** Prior to the adoption of a Draft Specification as an Approved Specification, Contributor may exclude Necessary Claims from its licensing commitments under this Agreement by issuing an Exclusion Notice. Contributor may not issue an Exclusion Notice for patents that were eligible to have been excluded pursuant to Section 3.1.
|
||||
|
||||
**4. Source Code License.** Any source code developed by the Working Group is solely subject the source code license included in the Working Group’s repository for that code. If no source code license is included, the source code will be subject to the MIT License.
|
||||
|
||||
**5. No Other Rights.** Except as specifically set forth in this License, no other express or implied patent, trademark, copyright, or other rights are granted under this License, including by implication, waiver, or estoppel.
|
||||
|
||||
**6. Antitrust Compliance.** Contributor acknowledge that it may compete with other participants in various lines of business and that it is therefore imperative that they and their respective representatives act in a manner that does not violate any applicable antitrust laws and regulations. This License does not restrict any Contributor from engaging in similar specification development projects. Each Contributor may design, develop, manufacture, acquire or market competitive deliverables, products, and services, and conduct its business, in whatever way it chooses. No Contributor is obligated to announce or market any products or services. Without limiting the generality of the foregoing, the Contributors agree not to have any discussion relating to any product pricing, methods or channels of product distribution, division of markets, allocation of customers or any other topic that should not be discussed among competitors under the auspices of the Working Group.
|
||||
|
||||
**7. Non-Circumvention.** Contributor agrees that it will not intentionally take or willfully assist any third party to take any action for the purpose of circumventing any obligations under this License.
|
||||
|
||||
**8. Representations, Warranties and Disclaimers.**
|
||||
|
||||
**8.1. Representations, Warranties and Disclaimers.** Contributor and Licensee represents and warrants that 1) it is legally entitled to grant the rights set forth in this License and 2) it will not intentionally include any third party materials in any Contribution unless those materials are available under terms that do not conflict with this License. IN ALL OTHER RESPECTS ITS CONTRIBUTIONS ARE PROVIDED "AS IS." The entire risk as to implementing or otherwise using the Contribution or the Specification is assumed by the implementer and user. Except as stated herein, CONTRIBUTOR AND LICENSEE EXPRESSLY DISCLAIM ANY WARRANTIES (EXPRESS, IMPLIED, OR OTHERWISE), INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, CONDITIONS OF QUALITY, OR TITLE, RELATED TO THE CONTRIBUTION OR THE SPECIFICATION. IN NO EVENT WILL ANY PARTY BE LIABLE TO ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND WITH RESPECT TO THIS AGREEMENT, WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR NOT THE OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Any obligations regarding the transfer, successors in interest, or assignment of Necessary Claims will be satisfied if Contributor or Licensee notifies the transferee or assignee of any patent that it knows contains Necessary Claims or necessary claims under this License. Nothing in this License requires Contributor to undertake a patent search. If Contributor is 1) employed by or acting on behalf of an employer, 2) is making a Contribution under the direction or control of a third party, or 3) is making the Contribution as a consultant, contractor, or under another similar relationship with a third party, Contributor represents that they have been authorized by that party to enter into this License on its behalf.
|
||||
|
||||
**8.2. Distribution Disclaimer.** Any distributions of technical information to third parties must include a notice materially similar to the following: “THESE MATERIALS ARE PROVIDED “AS IS.” The Contributors and Licensees expressly disclaim any warranties (express, implied, or otherwise), including implied warranties of merchantability, non-infringement, fitness for a particular purpose, or title, related to the materials. The entire risk as to implementing or otherwise using the materials is assumed by the implementer and user. IN NO EVENT WILL THE CONTRIBUTORS OR LICENSEES BE LIABLE TO ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND WITH RESPECT TO THIS DELIVERABLE OR ITS GOVERNING AGREEMENT, WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR NOT THE OTHER MEMBER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.”
|
||||
|
||||
**9. Definitions.**
|
||||
|
||||
**9.1. Affiliate.** “Affiliate” means an entity that directly or indirectly Controls, is Controlled by, or is under common Control of that party.
|
||||
|
||||
**9.2. Approved Specification.** “Approved Specification” means the final version and contents of any Draft Specification designated as an Approved Specification as set forth in the accompanying Governance.md file.
|
||||
|
||||
**9.3. Contribution.** “Contribution” means any original work of authorship, including any modifications or additions to an existing work, that Contributor submits for inclusion in a Draft Specification, which is included in a Draft Specification or Approved Specification.
|
||||
|
||||
**9.4. Contributor.** “Contributor” means any person or entity that has indicated its acceptance of the License 1) by making a Contribution to the Specification, or 2) by entering into the Community Specification Contributor License Agreement for the Specification. Contributor includes its Affiliates, assigns, agents, and successors in interest.
|
||||
|
||||
**9.5. Control.** “Control” means direct or indirect control of more than 50% of the voting power to elect directors of that corporation, or for any other entity, the power to direct management of such entity.
|
||||
|
||||
**9.6. Draft Specification.** “Draft Specification” means all versions of the material (except an Approved Specification) developed by this Working Group for the purpose of creating, commenting on, revising, updating, modifying, or adding to any document that is to be considered for inclusion in the Approved Specification.
|
||||
|
||||
**9.7. Exclusion Notice.** “Exclusion Notice” means a written notice made by making a pull request or commit to the repository’s Notices.md file that identifies patents that Contributor is excluding from its patent licensing commitments under this License. The Exclusion Notice for issued patents and published applications must include the Draft Specification’s name, patent number(s) or title and application number(s), as the case may be, for each of the issued patent(s) or pending patent application(s) that the Contributor is excluding from the royalty-free licensing commitment set forth in this License. If an issued patent or pending patent application that may contain Necessary Claims is not set forth in the Exclusion Notice, those Necessary Claims shall continue to be subject to the licensing commitments under this License. The Exclusion Notice for unpublished patent applications must provide either: (i) the text of the filed application; or (ii) identification of the specific part(s) of the Draft Specification whose implementation makes the excluded claim a Necessary Claim. If (ii) is chosen, the effect of the exclusion will be limited to the identified part(s) of the Draft Specification.
|
||||
|
||||
**9.8. Implementation.** “Implementation” means making, using, selling, offering for sale, importing or distributing any implementation of the Specification 1) only to the extent it implements the Specification and 2) so long as all required portions of the Specification are implemented.
|
||||
|
||||
**9.9. License.** “License” means this Community Specification License.
|
||||
|
||||
**9.10. Licensee.** “Licensee” means any person or entity that has indicated its acceptance of the License as set forth in Section 2.1.3. Licensee includes its Affiliates, assigns, agents, and successors in interest.
|
||||
|
||||
**9.11. Necessary Claims.** “Necessary Claims” are those patent claims, if any, that a party owns or controls, including those claims later acquired, that are necessary to implement the required portions (including the required elements of optional portions) of the Specification that are described in detail and not merely referenced in the Specification.
|
||||
|
||||
**9.12. Specification.** “Specification” means a Draft Specification or Approved Specification included in the Working Group’s repository subject to this License, and the version of the Specification implemented by the Licensee.
|
||||
|
||||
**9.13. Scope.** “Scope” has the meaning as set forth in the accompanying Scope.md file included in this Specification’s repository. Changes to Scope do not apply retroactively. If no Scope is provided, each Contributor’s Necessary Claims are limited to that Contributor’s Contributions.
|
||||
|
||||
**9.14. Working Group.** “Working Group” means this project to develop specifications, standards, best practices, guidelines, and other similar materials under this License.
|
||||
|
||||
|
||||
|
||||
*The text of this Community Specification License is Copyright 2020 Joint Development Foundation and is licensed under the Creative Commons Attribution 4.0 International License available at https://creativecommons.org/licenses/by/4.0/.*
|
||||
|
||||
SPDX-License-Identifier: CC-BY-4.0
|
||||
58
options/license-bundles/CSL-1.0/NOTICES.md
Normal file
58
options/license-bundles/CSL-1.0/NOTICES.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Notices
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Contact for Code of Conduct issues or inquires: _________________
|
||||
|
||||
[Ideally list two different individuals above (not a generic mailing list) as someone submitting a Code of Conduct complaint will want to know exactly who is receiving the complaint. We recommend two individuals in the case one of the individuals is the subject of or directly involved in the subject of a complaint.]
|
||||
|
||||
|
||||
## License Acceptance
|
||||
|
||||
Per Community Specification License 1.0 Section 2.1.3.3, Licensees may indicate their acceptance of the Community Specification License by issuing a pull request to the Specification’s repository’s Notice.md file, including the Licensee’s name, authorized individuals' names, and repository system identifier (e.g. GitHub ID), and specification version.
|
||||
|
||||
A Licensee may consent to accepting the current Community Specification License version or any future version of the Community Specification License by indicating "or later" after their specification version.
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
|
||||
Licensee’s name:
|
||||
|
||||
Authorized individual and system identifier:
|
||||
|
||||
Specification version:
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
|
||||
## Withdrawals
|
||||
|
||||
Name of party withdrawing:
|
||||
|
||||
Date of withdrawal:
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
|
||||
## Exclusions
|
||||
|
||||
This section includes any Exclusion Notices made against a Draft Deliverable or Approved Deliverable as set forth in the Community Specification Development License. Each Exclusion Notice must include the following information:
|
||||
|
||||
- Name of party making the Exclusion Notice:
|
||||
|
||||
- Name of patent owner:
|
||||
|
||||
- Specification:
|
||||
|
||||
- Version number:
|
||||
|
||||
**For issued patents and published patent applications:**
|
||||
|
||||
(i) patent number(s) or title and application number(s), as the case may be:
|
||||
|
||||
(ii) identification of the specific part(s) of the Specification whose implementation makes the excluded claim a Necessary Claim.
|
||||
|
||||
**For unpublished patent applications must provide either:**
|
||||
|
||||
(i) the text of the filed application; or
|
||||
|
||||
(ii) identification of the specific part(s) of the Specification whose implementation makes the excluded claim a Necessary Claim.
|
||||
|
||||
-----------------------------------------------------------------------------------------
|
||||
326
options/license/ACL-1.0
Normal file
326
options/license/ACL-1.0
Normal file
@@ -0,0 +1,326 @@
|
||||
# Auditable Commercial License (ACL) v1.0
|
||||
|
||||
**A Source-Available Commercial License for the AI Era**
|
||||
|
||||
---
|
||||
|
||||
## Preamble
|
||||
|
||||
This license balances transparency with commercial sustainability. It grants the Licensee the right to inspect, audit, and locally modify source code while protecting the Licensor's ability to sustain development. It explicitly addresses the use of licensed code in artificial intelligence training, automated code generation, and derivative redistribution.
|
||||
|
||||
This license is **not** an open-source license and does not satisfy the Open Source Definition. It is a **source-available commercial license** that prioritizes auditability, defined commercial terms, and fair use.
|
||||
|
||||
To provide adopters with a defined long-term guarantee, each released version of the Licensed Work automatically converts to the Apache License, Version 2.0 on the Change Date defined in Section 5.2. This ensures that adopters are never permanently dependent on the Licensor's continued operation or good will with respect to any given version of the Licensed Work.
|
||||
|
||||
After the Change Date for any given version, that version is governed entirely by the Change License, and the restrictions in Section 2 — including the AI-training restrictions in Section 2.3 — no longer apply to that version. This trade-off is intentional: Licensors who require indefinite AI-training protection should evaluate whether this license is appropriate for their work.
|
||||
|
||||
---
|
||||
|
||||
## Definitions
|
||||
|
||||
**"Affiliate"** — Any entity that controls, is controlled by, or is under common control with a party, where "control" means direct or indirect ownership of more than fifty percent (50%) of the voting interests of such entity.
|
||||
|
||||
**"AI System"** — Any machine learning model, large language model, neural network, embedding system, retrieval-augmented generation system, code generation model, or other automated system that (i) is trained, fine-tuned, or evaluated on data, or (ii) produces code, text, images, audio, or other derivative outputs as a function of training on data.
|
||||
|
||||
**"AI Tool"** — A software product that embeds or invokes an AI System to assist the Licensee's personnel with development, analysis, or operational tasks (examples include code assistants, IDE integrations, and agentic development tools). An AI Tool may be operated by the Licensee, by a third-party vendor, or by both.
|
||||
|
||||
**"Change Date"** — With respect to any given version of the Licensed Work, the fourth (4th) anniversary of the date that version is first publicly released by the Licensor under this license, or such earlier date as the Licensor may specify for that version in writing or as accelerated under Section 5.6.
|
||||
|
||||
**"Change License"** — The Apache License, Version 2.0, as published by the Apache Software Foundation, available at `https://www.apache.org/licenses/LICENSE-2.0`.
|
||||
|
||||
**"Competing Product"** — A product or service that the Licensee markets as a substitute for, or drop-in replacement for, the Licensed Work.
|
||||
|
||||
**"Derivative Work"** — Any software that incorporates, adapts, translates, modifies, or is substantially based on the Licensed Work or any material portion thereof. Independently developed software that merely interoperates with the Licensed Work through its public interfaces is not a Derivative Work.
|
||||
|
||||
**"Hosted Service"** — Any offering that makes the functionality of the Licensed Work or a Derivative Work available to third parties over a network — directly or through agents, automated systems, or other intermediaries acting on the Licensee's behalf — including software-as-a-service, platform-as-a-service, managed service, application programming interface, or embedded offering, whether or not the Licensed Work is directly accessible to such third parties.
|
||||
|
||||
**"Internal Use"** — Use of the Licensed Work by the Licensee and its Affiliates within their own organizations, infrastructure, and operations, including use by the Licensee's employees and contractors in the course of their work for the Licensee, but excluding any provision of the Licensed Work's functionality to third parties as a Hosted Service.
|
||||
|
||||
**"Licensed Work"** — The software, source code, object code, documentation, configuration files, and associated materials distributed by the Licensor under this license, as identified in the applicable release.
|
||||
|
||||
**"Licensee"** — The individual or entity that obtains, accesses, or uses the Licensed Work under this license. Where the Licensee is an entity, the term includes its Affiliates solely for purposes of Internal Use under Section 1. References to the Licensee's actions include actions taken by automated agents, scripts, or systems operated by or on behalf of the Licensee; the Licensee is responsible for ensuring that such automated actions comply with this license.
|
||||
|
||||
**"Licensor"** — The individual or entity identified as the copyright holder in the Licensed Work's distribution materials.
|
||||
|
||||
**"Production Use"** — Use of the Licensed Work in support of the Licensee's live operations, customer-facing systems, or revenue-generating activities, as distinguished from evaluation, development, or testing use.
|
||||
|
||||
**"Third Party"** — Any individual or entity other than the Licensee and its Affiliates.
|
||||
|
||||
**"Tier Schedule"** — The separate document maintained by the Licensor that specifies the available commercial tiers, their pricing, their usage parameters, and any tier-specific terms, as referenced in Section 4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grant of Rights
|
||||
|
||||
Subject to the terms of this license and payment of any applicable fees under the Tier Schedule, the Licensor grants the Licensee a **non-exclusive, non-transferable** (except as provided in Section 8.3) license to:
|
||||
|
||||
### 1.1 Source Inspection and Audit
|
||||
Read, review, and audit the source code of the Licensed Work for purposes of security evaluation, compliance verification, operational understanding, and internal technical review.
|
||||
|
||||
### 1.2 Internal Use
|
||||
Install, execute, and use the Licensed Work for Internal Use in accordance with the applicable tier defined in the Tier Schedule.
|
||||
|
||||
### 1.3 Local Modification
|
||||
Create modifications to the Licensed Work for Internal Use only, provided such modifications are not distributed, sublicensed, or made available to Third Parties except as permitted under Sections 2 and 4.
|
||||
|
||||
### 1.4 Integration
|
||||
Integrate the Licensed Work with the Licensee's own systems, applications, and workflows for Internal Use.
|
||||
|
||||
### 1.5 Patent License
|
||||
The Licensor grants the Licensee a non-exclusive, worldwide, royalty-free license under any patents owned or controlled by the Licensor that would otherwise be infringed by the Licensee's exercise of the rights granted in Sections 1.1 through 1.4. This patent license terminates automatically if the Licensee initiates patent litigation against the Licensor or any other Licensee alleging that the Licensed Work infringes a patent.
|
||||
|
||||
---
|
||||
|
||||
## 2. Restrictions
|
||||
|
||||
### 2.1 No Redistribution
|
||||
The Licensee may not redistribute, sublicense, sell, lease, publish, or otherwise transfer the Licensed Work, its source code, or any Derivative Work to any Third Party, except as expressly permitted by a separate written agreement with the Licensor or by the OEM / Embedding tier defined in Section 4.4.
|
||||
|
||||
### 2.2 No Hosted Service
|
||||
The Licensee may not make the Licensed Work or any Derivative Work available to Third Parties as a Hosted Service. For the avoidance of doubt, this restriction is objective and does not depend on the Licensor's discretion: any offering meeting the definition of Hosted Service is prohibited absent a separate written agreement.
|
||||
|
||||
### 2.3 No AI Training
|
||||
|
||||
**(a) Prohibited Uses.** The Licensee may not knowingly:
|
||||
|
||||
1. Use the Licensed Work, including its source code, documentation, architecture, or test data, as training data, fine-tuning data, reinforcement signal, or evaluation data for any AI System;
|
||||
2. Submit the Licensed Work to any AI System for the purpose of generating code, designs, or architectures intended to replicate, approximate, or be derived from the Licensed Work for use outside the scope of this license; or
|
||||
3. Use any AI System to extract, summarize, or reproduce the functional logic, structural patterns, or implementation details of the Licensed Work for use in any project other than the Licensee's permitted Internal Use.
|
||||
|
||||
**(b) Permitted AI Tool Use.** The Licensee may use AI Tools to work with the Licensed Work in the course of permitted Internal Use — for example, to understand, debug, modify, or extend the Licensee's own local copies — provided that:
|
||||
|
||||
1. The AI Tool is operated under terms that do not, by default, incorporate the Licensee's inputs into the training or fine-tuning of the AI Tool's underlying AI System;
|
||||
2. The Licensee does not intentionally configure the AI Tool to contribute the Licensed Work to training data; and
|
||||
3. The Licensee takes reasonable measures, consistent with industry practice at the time, to prevent inadvertent contribution of the Licensed Work to AI System training. The Licensee is not responsible for AI Tool vendor behavior that the Licensee could not reasonably have known about or controlled.
|
||||
|
||||
**(c) Safe Harbor.** Use of an AI Tool that is operated under an enterprise, business, or commercial agreement whose default terms exclude customer inputs from AI System training shall be deemed to satisfy Section 2.3(b) absent the Licensee's actual knowledge to the contrary.
|
||||
|
||||
**(d) Flow-Through Obligation.** The Licensee shall not enter into an agreement with any AI Tool vendor that requires the Licensee to permit the use of the Licensed Work as training data. Where the Licensee becomes aware that an AI Tool it uses has incorporated the Licensed Work into training data in breach of this section, the Licensee shall promptly notify the Licensor and take commercially reasonable steps to cause the AI Tool vendor to cease such use.
|
||||
|
||||
### 2.4 No Obfuscation of Origin
|
||||
The Licensee may not remove, alter, or obscure any copyright notices, license references, or attribution statements included in the Licensed Work.
|
||||
|
||||
---
|
||||
|
||||
## 3. Contributions
|
||||
|
||||
### 3.1 Grant of Rights in Contributions
|
||||
If the Licensee submits modifications, improvements, extensions, bug fixes, or other materials to the Licensor (each, a "Contribution"), whether voluntarily or through an agreed contribution mechanism, the Licensee grants the Licensor:
|
||||
|
||||
**(a)** a perpetual, irrevocable, worldwide, royalty-free, sublicensable copyright license to use, reproduce, modify, distribute, and sublicense the Contribution as part of the Licensed Work under any license of the Licensor's choosing; and
|
||||
|
||||
**(b)** a perpetual, irrevocable, worldwide, royalty-free, sublicensable patent license under any patents owned or controlled by the Licensee that would otherwise be infringed by the incorporation or use of the Contribution in the Licensed Work.
|
||||
|
||||
### 3.2 No Obligation to Accept
|
||||
The Licensor is under no obligation to accept, integrate, or maintain any Contribution submitted by the Licensee.
|
||||
|
||||
### 3.3 Contributor Recognition
|
||||
The Licensor will make reasonable efforts to credit contributors in release notes or documentation, unless the contributor requests anonymity.
|
||||
|
||||
### 3.4 Representations
|
||||
The Licensee represents that it has the legal right to submit each Contribution and to grant the licenses described in Section 3.1, and that each Contribution is either the Licensee's original work or is properly attributed and licensed from its original source.
|
||||
|
||||
### 3.5 Acceptance Mechanism
|
||||
Contributions must be submitted with a Developer Certificate of Origin (DCO) sign-off affirming the Contributor's authority to grant the licenses described in Section 3.1. The Licensor may also require contributors to execute a separate Contributor License Agreement before accepting Contributions.
|
||||
|
||||
---
|
||||
|
||||
## 4. Commercial Tiers
|
||||
|
||||
The Licensed Work is offered under commercial tiers described in a separate **Tier Schedule** maintained by the Licensor. The Tier Schedule specifies the available tiers, pricing, usage parameters, support levels, and any tier-specific terms. At minimum, the following tiers are contemplated:
|
||||
|
||||
### 4.1 Community Tier (Free)
|
||||
- Access to compiled or binary releases
|
||||
- Community documentation
|
||||
- No source code access
|
||||
- No support guarantee
|
||||
- Internal Use only; no Production Use by for-profit organizations with more than the headcount specified in the Tier Schedule
|
||||
|
||||
### 4.2 Professional Tier (Paid)
|
||||
- Full source code access (read, audit, local modification)
|
||||
- Standard support channels
|
||||
- Internal Use up to the user or instance count specified in the Tier Schedule
|
||||
- Production Use permitted, subject to the user or instance count specified in the Tier Schedule
|
||||
- Updates and patches during the subscription period
|
||||
|
||||
### 4.3 Enterprise Tier (Paid)
|
||||
- Full source code access with extended modification rights
|
||||
- Priority support and defined service levels
|
||||
- Internal Use without user or instance limits
|
||||
- Right to request scoped custom modifications
|
||||
- Deployment assistance and consultation
|
||||
- Extended audit and compliance documentation
|
||||
- Limited IP indemnification as set forth in Section 6.2
|
||||
|
||||
### 4.4 OEM / Embedding Tier (Negotiated)
|
||||
- Right to embed the Licensed Work within the Licensee's own products
|
||||
- Distribution and sublicensing rights under separately negotiated terms
|
||||
- Co-branding or white-label options
|
||||
- Custom commercial terms, which may override provisions of this license as expressly specified in the governing agreement
|
||||
|
||||
---
|
||||
|
||||
## 5. Term, Change Date, and Termination
|
||||
|
||||
### 5.1 Term
|
||||
This license is effective from the date the Licensee first accesses or uses the Licensed Work and continues for the duration of the applicable subscription or agreement under the Tier Schedule, subject to the Change Date provision in Section 5.2 and the termination provisions in Section 5.3.
|
||||
|
||||
### 5.2 Change Date (Automatic Conversion to Apache 2.0)
|
||||
Effective on the Change Date applicable to any version of the Licensed Work, that version — and only that version — shall be re-licensed and made available to the Licensee and to the public under the Change License (Apache License, Version 2.0). From and after the Change Date for a given version:
|
||||
|
||||
**(a)** the Licensee's use of that version is governed by the Change License rather than this license;
|
||||
|
||||
**(b)** the restrictions in Section 2 no longer apply to that version; and
|
||||
|
||||
**(c)** subsequent versions of the Licensed Work released by the Licensor may continue to be distributed under this license, each with its own Change Date.
|
||||
|
||||
The Licensor may, at its discretion, accelerate the Change Date for any version by publishing written notice to that effect. The Licensor may not extend or revoke the Change Date for a version once that version has been publicly released.
|
||||
|
||||
### 5.3 Termination for Breach
|
||||
The Licensor may terminate this license, in whole or in part, upon written notice if the Licensee materially breaches this license and fails to cure such breach within thirty (30) days after receiving written notice of the breach. Breaches of Section 2.1 (No Redistribution), Section 2.2 (No Hosted Service), or Section 2.3 (No AI Training) are deemed material.
|
||||
|
||||
### 5.4 Effect of Termination and Wind-Down
|
||||
Upon termination of this license other than conversion under Section 5.2:
|
||||
|
||||
**(a) Immediate Obligations.** The Licensee shall cease new deployments of the Licensed Work, cease granting new internal user access, and cease any activities that caused the termination.
|
||||
|
||||
**(b) Wind-Down Period.** Unless the termination is for a willful or repeated material breach, the Licensee shall have a wind-down period to migrate away from the Licensed Work. The wind-down period is thirty (30) days for the Community Tier, ninety (90) days for the Professional Tier, and twelve (12) months for the Enterprise Tier and OEM Tier. During the wind-down period, the Licensee may continue Production Use of the Licensed Work strictly to maintain existing deployments while migrating, but may not expand such use.
|
||||
|
||||
**(c) Return or Destruction.** At the end of the wind-down period, the Licensee shall destroy or return all copies of the Licensed Work and its source code in the Licensee's possession, except that the Licensee may retain a single archival copy to the extent required by applicable law, regulatory obligation, or internal audit policy, provided such archival copy is not used for any active purpose.
|
||||
|
||||
**(d) Versions Past Change Date.** Termination of this license does not affect the Licensee's rights under the Change License with respect to any version of the Licensed Work whose Change Date has already occurred.
|
||||
|
||||
### 5.5 Survival
|
||||
Sections 2 (Restrictions, until the applicable Change Date for any given version), 3.1 (Grant of Rights in Contributions), 6 (Warranties and IP Indemnification), 7 (Limitation of Liability), and 8 (General Provisions) survive termination or expiration of this license.
|
||||
|
||||
### 5.6 Continuity Event
|
||||
A "Continuity Event" occurs upon any of the following: (i) the Licensor ceases commercial distribution and support of the Licensed Work for one hundred eighty (180) consecutive days; (ii) the Licensor files a voluntary petition under bankruptcy or insolvency law, or has an involuntary petition filed against it that is not dismissed within sixty (60) days; or (iii) the Licensor publicly announces end-of-life of the Licensed Work without identifying a successor licensor.
|
||||
|
||||
Upon a Continuity Event, the Change Date for every then-current version of the Licensed Work shall be automatically accelerated to thirty (30) days after the Continuity Event. From and after that accelerated Change Date, those versions shall be governed by the Change License in accordance with Section 5.2.
|
||||
|
||||
---
|
||||
|
||||
## 6. Warranties and IP Indemnification
|
||||
|
||||
### 6.1 Disclaimer of Warranties
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN SECTION 6.2, THE LICENSED WORK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ACCURACY, OR UNINTERRUPTED OPERATION. THE LICENSEE ASSUMES ALL RISK ARISING FROM ITS USE OF THE LICENSED WORK.
|
||||
|
||||
### 6.2 Limited IP Indemnification (Enterprise and OEM Tiers)
|
||||
For Licensees under the Enterprise Tier or OEM Tier and in good standing with respect to payment obligations, the Licensor shall defend and indemnify the Licensee against any Third Party claim that the Licensed Work, when used in accordance with this license, infringes any valid patent, copyright, trademark, or trade secret, and shall pay damages finally awarded by a court of competent jurisdiction or agreed in settlement, subject to the following:
|
||||
|
||||
**(a) Conditions.** The Licensee must (i) promptly notify the Licensor in writing of the claim, (ii) grant the Licensor sole control of the defense and settlement, and (iii) provide reasonable cooperation at the Licensor's expense.
|
||||
|
||||
**(b) Exclusions.** The Licensor has no obligation under this Section 6.2 to the extent a claim arises from (i) modifications to the Licensed Work not made or authorized by the Licensor, (ii) combination of the Licensed Work with other software or systems not provided or authorized by the Licensor, (iii) use of the Licensed Work in breach of this license, or (iv) use of a version of the Licensed Work after the Licensor has made a non-infringing version available to the Licensee.
|
||||
|
||||
**(c) Remedies.** If the Licensed Work is held to infringe, or the Licensor reasonably believes it is likely to be held to infringe, the Licensor may at its option (i) procure the right for the Licensee to continue using the Licensed Work, (ii) modify the Licensed Work to be non-infringing, or (iii) terminate the affected license and refund the prorated fees paid for the then-current subscription term.
|
||||
|
||||
**(d) Exclusive Remedy.** This Section 6.2 states the Licensee's exclusive remedy and the Licensor's entire liability for intellectual property infringement claims.
|
||||
|
||||
### 6.3 Mutual Patent Termination
|
||||
If the Licensee or any of its Affiliates initiates patent litigation (including a cross-claim or counterclaim) against the Licensor or any other Licensee alleging that the Licensed Work infringes a patent, then all licenses granted to the Licensee under Sections 1, 2, and 6.2 shall terminate immediately.
|
||||
|
||||
---
|
||||
|
||||
## 7. Limitation of Liability
|
||||
|
||||
### 7.1 Exclusion of Indirect Damages
|
||||
IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, INCLUDING BUT NOT LIMITED TO LOST PROFITS, LOST REVENUE, LOST DATA, BUSINESS INTERRUPTION, OR COST OF SUBSTITUTE GOODS OR SERVICES, ARISING OUT OF OR RELATING TO THIS LICENSE OR THE LICENSED WORK, WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, AND EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
### 7.2 Liability Cap
|
||||
THE LICENSOR'S TOTAL CUMULATIVE LIABILITY ARISING OUT OF OR RELATING TO THIS LICENSE SHALL NOT EXCEED THE AMOUNTS PAID BY THE LICENSEE TO THE LICENSOR UNDER THE APPLICABLE TIER IN THE TWELVE (12) MONTHS IMMEDIATELY PRECEDING THE EVENT GIVING RISE TO THE CLAIM.
|
||||
|
||||
### 7.3 Exceptions
|
||||
The limitations in this Section 7 do not apply to (i) the Licensor's indemnification obligations under Section 6.2, (ii) breaches of confidentiality obligations under a separate agreement, or (iii) liability that cannot be limited under applicable law.
|
||||
|
||||
---
|
||||
|
||||
## 8. General Provisions
|
||||
|
||||
### 8.1 Governing Law and Venue
|
||||
This license shall be governed by and construed in accordance with the laws of [Jurisdiction], without regard to its conflict-of-law principles. The parties irrevocably submit to the exclusive jurisdiction of the state and federal courts located in [Venue] for the resolution of any dispute arising out of or relating to this license, except as otherwise provided in Section 8.2.
|
||||
|
||||
### 8.2 Dispute Resolution
|
||||
The parties shall first attempt in good faith to resolve any dispute arising out of or relating to this license through direct negotiation between executives with authority to settle the dispute. If the dispute is not resolved within sixty (60) days of written notice of the dispute, either party may initiate proceedings in the courts identified in Section 8.1. Nothing in this Section 8.2 prevents either party from seeking injunctive relief at any time to protect its intellectual property or confidential information.
|
||||
|
||||
### 8.3 Assignment and Change of Control
|
||||
The Licensee may assign this license to a successor in interest in connection with a merger, acquisition, reorganization, or sale of all or substantially all of the Licensee's assets, upon written notice to the Licensor.
|
||||
|
||||
If, at the time of assignment, the successor entity is engaged in developing or marketing a Competing Product, then within thirty (30) days of the Licensor's written request, the parties shall negotiate in good faith either (i) a wind-down of the successor's use of the Licensed Work over a period not to exceed twelve (12) months, or (ii) a new license agreement on commercial terms appropriate to the successor's circumstances. If the parties have not reached agreement within ninety (90) days of the Licensor's request, the Licensor may terminate the license on ninety (90) days' written notice to the successor.
|
||||
|
||||
Other assignments by the Licensee require the prior written consent of the Licensor. The Licensor may assign this license to any successor to its business or to an Affiliate without notice or consent.
|
||||
|
||||
### 8.4 Third-Party Components
|
||||
The Licensed Work may incorporate third-party open-source or commercial components, each of which is governed by its own license terms. Such components are identified in a NOTICE or THIRD_PARTY_LICENSES file distributed with the Licensed Work. Nothing in this license alters the terms under which such third-party components are licensed.
|
||||
|
||||
### 8.5 Compliance with Laws and Export Controls
|
||||
Each party shall comply with all laws applicable to its performance under this license. The Licensee represents that it is not located in, and will not use, export, or re-export the Licensed Work to, any country or Person subject to comprehensive economic sanctions administered by the United States, the European Union, or the United Kingdom, and that it is not identified on any restricted-party list maintained by such authorities.
|
||||
|
||||
### 8.6 Data Protection
|
||||
The Licensor is not a data processor with respect to any personal data processed by the Licensee through its use of the Licensed Work. The Licensee is solely responsible for compliance with applicable data-protection laws in its use of the Licensed Work.
|
||||
|
||||
### 8.7 Notices
|
||||
All notices required under this license must be in writing and delivered to the address designated by the receiving party in the applicable Tier Schedule or subscription agreement, or — where no such address has been designated — to the Licensor's published contact address. Notices are effective upon receipt, or three (3) business days after dispatch if sent by registered mail or recognized courier service.
|
||||
|
||||
### 8.8 Force Majeure
|
||||
Neither party shall be liable for any failure or delay in performance (other than payment obligations) caused by events beyond its reasonable control, including acts of God, war, terrorism, civil disturbance, labor dispute, utility failure, pandemic, or government action. The affected party shall promptly notify the other and use reasonable efforts to resume performance.
|
||||
|
||||
### 8.9 Severability
|
||||
If any provision of this license is held unenforceable by a court of competent jurisdiction, that provision shall be modified to the minimum extent necessary to make it enforceable, and the remaining provisions shall continue in full force and effect.
|
||||
|
||||
### 8.10 Entire Agreement
|
||||
This license, together with the applicable Tier Schedule, subscription agreement, and any separately negotiated terms for OEM Tier Licensees, constitutes the entire agreement between the parties regarding the Licensed Work and supersedes all prior or contemporaneous communications, representations, and agreements on that subject matter.
|
||||
|
||||
### 8.11 Amendment of This License
|
||||
The Licensor may publish updated versions of this license for future releases of the Licensed Work. Existing Licensees remain bound by the version of this license in effect at the time of their current subscription term, and will be offered the opportunity to accept or reject the updated version at the time of their next renewal.
|
||||
|
||||
### 8.12 No Partnership
|
||||
Nothing in this license creates a partnership, joint venture, employment, franchise, or agency relationship between the parties. Neither party has authority to bind the other.
|
||||
|
||||
### 8.13 Relationship to Tier Schedule
|
||||
In the event of any conflict between this license and the Tier Schedule, this license controls, except where the Tier Schedule expressly identifies a specific provision of this license and states that it is modified for the applicable tier. OEM Tier agreements may expressly override provisions of this license as specified in the governing OEM agreement.
|
||||
|
||||
The Licensor may update the Tier Schedule from time to time. Changes that expand Licensee rights or reduce fees take effect immediately upon publication. Changes that restrict Licensee rights or increase fees take effect at the Licensee's next renewal following ninety (90) days' notice, except where required by law or by the Licensor's third-party obligations.
|
||||
|
||||
---
|
||||
|
||||
## Attribution
|
||||
|
||||
This license is published as the **Auditable Commercial License (ACL) v1.0**. Other projects may adopt, adapt, or reference this license with attribution to the original authors.
|
||||
|
||||
### Suggested Notice for Source Files
|
||||
|
||||
```
|
||||
Copyright (c) [Year] [Licensor Name]
|
||||
Licensed under the Auditable Commercial License (ACL) v1.0
|
||||
See LICENSE.md for terms. This is not open-source software.
|
||||
On the Change Date specified in Section 5.2, this version
|
||||
converts to the Apache License, Version 2.0.
|
||||
```
|
||||
|
||||
### Suggested Notice for Distribution Packages
|
||||
|
||||
```
|
||||
This software is distributed under the Auditable Commercial
|
||||
License (ACL) v1.0 — a source-available commercial license.
|
||||
Full terms: [URL to LICENSE.md]
|
||||
Tier details: [URL to Tier Schedule]
|
||||
Change Date for this version: [YYYY-MM-DD]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Drafting Notes for Adoption
|
||||
|
||||
The following items must be customized before this license is applied to a specific Licensed Work:
|
||||
|
||||
1. **Section 8.1 — Governing Law and Venue.** Fill in the jurisdiction and venue appropriate to the publishing entity. Common choices include Delaware (broad U.S. commercial precedent), Singapore (Asia-Pacific), or the jurisdiction of the Licensor's principal place of business.
|
||||
2. **Section 4 — Tier Schedule.** Publish or link to a Tier Schedule defining user counts, pricing, and support terms for each tier.
|
||||
3. **Section 8.7 — Notices.** Specify the Licensor's contact address for legal notices.
|
||||
4. **Per-Version Change Date.** For each released version, record the Change Date in the release metadata (the default being the fourth anniversary of public release, subject to acceleration under Section 5.6).
|
||||
5. **Third-Party Components.** Maintain a NOTICE or THIRD_PARTY_LICENSES file listing embedded open-source or commercial components.
|
||||
|
||||
---
|
||||
|
||||
*Auditable Commercial License (ACL) v1.0*
|
||||
|
||||
*This license should be reviewed by qualified legal counsel in the Licensor's jurisdiction before being applied to any production Licensed Work. This document is drafted as a polished commercial license template and is intended to minimize the review effort required of counsel, but it is not a substitute for that review.*
|
||||
96
options/license/BUSL-1.1
Normal file
96
options/license/BUSL-1.1
Normal file
@@ -0,0 +1,96 @@
|
||||
Business Source License 1.1
|
||||
|
||||
Parameters
|
||||
|
||||
Licensor: <copyright holders>
|
||||
Licensed Work: <program>
|
||||
The Licensed Work is (c) <year> <copyright holders>
|
||||
|
||||
Additional Use Grant: None
|
||||
|
||||
Change Date: Four years from the date the Licensed Work is published.
|
||||
|
||||
Change License: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements for the Licensed Work,
|
||||
please contact the Licensor.
|
||||
|
||||
Notice
|
||||
|
||||
The Business Source License (this document, or the "License") is not an Open
|
||||
Source license. However, the Licensed Work will eventually be made available
|
||||
under an Open Source License, as stated in this License.
|
||||
|
||||
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
||||
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Business Source License 1.1
|
||||
|
||||
Terms
|
||||
|
||||
The Licensor hereby grants you the right to copy, modify, create derivative
|
||||
works, redistribute, and make non-production use of the Licensed Work. The
|
||||
Licensor may make an Additional Use Grant, above, permitting limited
|
||||
production use.
|
||||
|
||||
Effective on the Change Date, or the fourth anniversary of the first publicly
|
||||
available distribution of a specific version of the Licensed Work under this
|
||||
License, whichever comes first, the Licensor hereby grants you rights under
|
||||
the terms of the Change License, and the rights granted in the paragraph
|
||||
above terminate.
|
||||
|
||||
If your use of the Licensed Work does not comply with the requirements
|
||||
currently in effect as described in this License, you must purchase a
|
||||
commercial license from the Licensor, its affiliated entities, or authorized
|
||||
resellers, or you must refrain from using the Licensed Work.
|
||||
|
||||
All copies of the original and modified Licensed Work, and derivative works
|
||||
of the Licensed Work, are subject to this License. This License applies
|
||||
separately for each version of the Licensed Work and the Change Date may vary
|
||||
for each version of the Licensed Work released by Licensor.
|
||||
|
||||
You must conspicuously display this License on each original or modified copy
|
||||
of the Licensed Work. If you receive the Licensed Work in original or
|
||||
modified form from a third party, the terms and conditions set forth in this
|
||||
License apply to your use of that work.
|
||||
|
||||
Any use of the Licensed Work in violation of this License will automatically
|
||||
terminate your rights under this License for the current and all other
|
||||
versions of the Licensed Work.
|
||||
|
||||
This License does not grant you any right in any trademark or logo of
|
||||
Licensor or its affiliates (provided that you may use a trademark or logo of
|
||||
Licensor as expressly required by this License).
|
||||
|
||||
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
||||
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
||||
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
||||
TITLE.
|
||||
|
||||
MariaDB hereby grants you permission to use this License's text to license
|
||||
your works, and to refer to it using the trademark "Business Source License",
|
||||
as long as you comply with the Covenants of Licensor below.
|
||||
|
||||
Covenants of Licensor
|
||||
|
||||
In consideration of the right to use this License's text and the "Business
|
||||
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
||||
other recipients of the licensed work to be provided by Licensor:
|
||||
|
||||
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
||||
or a license that is compatible with GPL Version 2.0 or a later version,
|
||||
where "compatible" means that software provided under the Change License can
|
||||
be included in a program with software provided under GPL Version 2.0 or a
|
||||
later version. Licensor may specify additional Change Licenses without
|
||||
limitation.
|
||||
|
||||
2. To either: (a) specify an additional grant of rights to use that does not
|
||||
impose any additional restriction on the right granted in this License, as
|
||||
the Additional Use Grant; or (b) insert the text "None".
|
||||
|
||||
3. To specify a Change Date.
|
||||
|
||||
4. Not to modify this License in any other way.
|
||||
158
options/license/CC-BY-NC-4.0
Normal file
158
options/license/CC-BY-NC-4.0
Normal file
@@ -0,0 +1,158 @@
|
||||
Creative Commons Attribution-NonCommercial 4.0 International
|
||||
|
||||
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
|
||||
|
||||
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
|
||||
|
||||
Creative Commons Attribution-NonCommercial 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||
|
||||
Section 1 – Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
|
||||
|
||||
i. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
|
||||
|
||||
j. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||
|
||||
k. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
l. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||
|
||||
Section 2 – Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
|
||||
|
||||
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
|
||||
|
||||
Section 3 – License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified form), You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
|
||||
|
||||
Section 4 – Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||
|
||||
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
|
||||
|
||||
b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
Section 6 – Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
Section 7 – Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||
|
||||
Section 8 – Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
154
options/license/CC-BY-ND-4.0
Normal file
154
options/license/CC-BY-ND-4.0
Normal file
@@ -0,0 +1,154 @@
|
||||
Creative Commons Attribution-NoDerivatives 4.0 International
|
||||
|
||||
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
|
||||
|
||||
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
|
||||
|
||||
Creative Commons Attribution-NoDerivatives 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||
|
||||
Section 1 – Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||
|
||||
b. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
c. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||
|
||||
d. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||
|
||||
e. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||
|
||||
f. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
g. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
|
||||
|
||||
h. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||
|
||||
i. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
j. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||
|
||||
Section 2 – Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||
|
||||
B. produce and reproduce, but not Share, Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
|
||||
|
||||
Section 3 – License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material, You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material.
|
||||
|
||||
3. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
4. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
Section 4 – Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database, provided You do not Share Adapted Material;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||
|
||||
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
|
||||
|
||||
b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
Section 6 – Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||
|
||||
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
Section 7 – Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||
|
||||
Section 8 – Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2303,6 +2303,47 @@
|
||||
"repo.settings.mirror_settings.push_mirror.none": "No push mirrors configured",
|
||||
"repo.settings.mirror_settings.push_mirror.remote_url": "Git Remote Repository URL",
|
||||
"repo.settings.mirror_settings.push_mirror.add": "Add Push Mirror",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files": "Exclude hidden files and folders",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files_desc": "Do not push files/folders starting with \".\" or marked as hidden to the remote mirror",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_tooltip": "Reset mirror to a specific commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_title": "Reset push mirror to commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_title": "Destructive action",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_body": "This will force-push the chosen commit to the remote and rewrite the branch's history there. Auto-sync will be paused so the next scheduled sync does not undo this.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch": "Branch on remote",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch_help": "The branch on the remote mirror to reset.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit": "Commit SHA",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit_help": "Full SHA of the commit in this repository to push to the remote.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_confirm": "Force-push and pause sync",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_required": "Branch and commit SHA are required.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_failed": "Failed to reset mirror: %s",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_success": "Mirror reset complete. Auto-sync has been paused; re-enable it via Edit when ready.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button": "Reset all mirrors to a commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button_help": "Force-push the same commit to every push mirror at once and pause all auto-sync.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_title": "Reset all push mirrors to commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_title": "Destructive action — affects all mirrors",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_body": "This will force-push the chosen commit to the named branch on every push mirror configured for this repository. Auto-sync will be paused on all of them.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_confirm": "Force-push all mirrors and pause sync",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_success": "Reset complete. %d mirror(s) updated and auto-sync paused.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_partial": "Reset partially complete: %d succeeded, %d failed. Errors: %s",
|
||||
"repo.settings.repo_reset.title": "Reset Repository History",
|
||||
"repo.settings.repo_reset.desc": "Move a branch's HEAD to a specific commit, dropping later commits, and force-push the change to every push mirror.",
|
||||
"repo.settings.repo_reset.button": "Reset to Commit",
|
||||
"repo.settings.repo_reset.warning_title": "This rewrites repository history",
|
||||
"repo.settings.repo_reset.warning_body": "Performing this action will:",
|
||||
"repo.settings.repo_reset.warning_branch": "Move the branch's HEAD on this server to the chosen commit. Commits after that point on this branch become unreachable.",
|
||||
"repo.settings.repo_reset.warning_collaborators": "Anyone who has cloned or pulled the repository will need to re-clone or hard-reset their local copy.",
|
||||
"repo.settings.repo_reset.warning_mirrors": "All push mirrors for this repository will be force-pushed to match, and their auto-sync will be paused.",
|
||||
"repo.settings.repo_reset.branch": "Branch to reset",
|
||||
"repo.settings.repo_reset.commit": "Commit SHA",
|
||||
"repo.settings.repo_reset.confirm_name": "To confirm, type the repository name: %s",
|
||||
"repo.settings.repo_reset.confirm": "Reset repository",
|
||||
"repo.settings.repo_reset.required": "Branch and commit SHA are required.",
|
||||
"repo.settings.repo_reset.confirm_mismatch": "The repository name confirmation did not match.",
|
||||
"repo.settings.repo_reset.commit_not_found": "Commit %s was not found in this repository.",
|
||||
"repo.settings.repo_reset.branch_not_found": "Branch %s does not exist in this repository.",
|
||||
"repo.settings.repo_reset.failed": "Failed to reset repository: %s",
|
||||
"repo.settings.repo_reset.success": "Repository reset complete. All push mirrors have been force-pushed and their auto-sync is paused.",
|
||||
"repo.settings.repo_reset.partial": "Repository was reset locally, but some mirrors failed to update: %s",
|
||||
"repo.settings.mirror_settings.push_mirror.edit_sync_time": "Edit mirror sync interval",
|
||||
"repo.settings.sync_mirror": "Synchronize Now",
|
||||
"repo.settings.pull_mirror_sync_in_progress": "Pulling changes from the remote %s at the moment.",
|
||||
@@ -4411,6 +4452,17 @@
|
||||
"repo.settings.license_apply": "Apply",
|
||||
"repo.settings.license_overwrite_warning": "This will overwrite the existing %s file.",
|
||||
"repo.settings.license_create_confirm": "This will create a LICENSE.md file in the repository.",
|
||||
"repo.settings.license_acl_fields": "Auditable Commercial License Settings",
|
||||
"repo.settings.license_acl_fields_desc": "These fields are required to customize the Auditable Commercial License (ACL) v1.0 for your project.",
|
||||
"repo.settings.license_acl_jurisdiction": "Jurisdiction",
|
||||
"repo.settings.license_acl_jurisdiction_help": "Governing law for Section 8.1 (e.g., \"the State of Delaware, United States\").",
|
||||
"repo.settings.license_acl_venue": "Venue",
|
||||
"repo.settings.license_acl_venue_help": "Court venue for disputes (e.g., \"Wilmington, Delaware\").",
|
||||
"repo.settings.license_acl_contact": "Contact Address (optional)",
|
||||
"repo.settings.license_acl_contact_help": "Where legal notices should be sent. Adds a Notices section if provided.",
|
||||
"repo.settings.license_acl_license_url": "License URL (optional)",
|
||||
"repo.settings.license_acl_tier_url": "Tier Schedule URL (optional)",
|
||||
"repo.settings.license_acl_required": "Jurisdiction and Venue are required for the Auditable Commercial License.",
|
||||
"repo.settings.license_copyright_holder": "Copyright holder",
|
||||
"repo.settings.gallery": "Gallery",
|
||||
"repo.settings.gallery_help": "Upload images to showcase your project. Images are stored in the .gallery folder.",
|
||||
@@ -4503,6 +4555,12 @@
|
||||
"repo.settings.pages.app_store_id_desc": "App ID from the App Store URL (e.g. id123456789)",
|
||||
"repo.settings.pages.stats": "Stats",
|
||||
"repo.settings.pages.value_props": "Value Propositions",
|
||||
"repo.settings.pages.value_props_headline": "Section Headline",
|
||||
"repo.settings.pages.value_props_headline_help": "Large heading for the value propositions section (e.g., \"Built for makers\"). The small label above it is set in Section Labels.",
|
||||
"repo.settings.pages.value_props_subheadline": "Section Subheadline",
|
||||
"repo.settings.pages.features_headline": "Section Headline",
|
||||
"repo.settings.pages.features_headline_help": "Large heading for the features section (e.g., \"Everything you need\"). The small label above it is set in Section Labels.",
|
||||
"repo.settings.pages.features_subheadline": "Section Subheadline",
|
||||
"repo.settings.pages.features": "Features",
|
||||
"repo.settings.pages.company_logos": "Company Logos",
|
||||
"repo.settings.pages.testimonials": "Testimonials",
|
||||
@@ -4545,6 +4603,12 @@
|
||||
"repo.settings.pages.nav_show_repository": "Show Repository link (View Source button)",
|
||||
"repo.settings.pages.nav_show_releases": "Show Releases link",
|
||||
"repo.settings.pages.nav_show_issues": "Show Issues link",
|
||||
"repo.settings.pages.section_labels": "Section Labels",
|
||||
"repo.settings.pages.section_labels_desc": "Customize the headings shown on your landing page for each section",
|
||||
"repo.settings.pages.label_value_props": "Value Props Heading",
|
||||
"repo.settings.pages.label_value_props_help": "Heading for the value propositions section (e.g., \"Why choose us\")",
|
||||
"repo.settings.pages.label_features": "Features Heading",
|
||||
"repo.settings.pages.label_features_help": "Heading for the features section (e.g., \"Capabilities\")",
|
||||
"repo.settings.pages.blog_section": "Blog Section",
|
||||
"repo.settings.pages.blog_enabled_desc": "Show recent blog posts on the landing page",
|
||||
"repo.settings.pages.blog_headline": "Blog Headline",
|
||||
@@ -4558,6 +4622,11 @@
|
||||
"repo.settings.pages.gallery_columns": "Grid Columns",
|
||||
"repo.settings.pages.gallery_help_link": "Upload and manage gallery images in Settings > Gallery.",
|
||||
"repo.settings.pages.comparison": "Comparison",
|
||||
"repo.settings.pages.cross_promote_section": "Cross-Promote Section",
|
||||
"repo.settings.pages.cross_promote_enabled_desc": "Show cross-promoted repositories on the landing page (only repos with landing pages enabled are shown)",
|
||||
"repo.settings.pages.cross_promote_headline": "Section Headline",
|
||||
"repo.settings.pages.cross_promote_subheadline": "Section Subheadline",
|
||||
"repo.settings.pages.cross_promote_help": "Configure which repositories to cross-promote in Settings > Cross-Promote. Only repos with landing pages enabled will appear.",
|
||||
"repo.settings.pages.comparison_section": "Comparison Section",
|
||||
"repo.settings.pages.comparison_enabled_desc": "Show a feature comparison table on the landing page",
|
||||
"repo.settings.pages.comparison_headline": "Headline",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,8 @@
|
||||
"template": "Template",
|
||||
"language": "Bahasa",
|
||||
"notifications": "Notifikasi",
|
||||
"create_new": "Buat…",
|
||||
"user_profile_and_more": "Profil dan Pengaturan…",
|
||||
"create_new": "Buat\u00e2\u20ac\u00a6",
|
||||
"user_profile_and_more": "Profil dan Pengaturan\u00e2\u20ac\u00a6",
|
||||
"signed_in_as": "Masuk sebagai",
|
||||
"toc": "Daftar Isi",
|
||||
"username": "Nama Pengguna",
|
||||
@@ -28,7 +28,7 @@
|
||||
"passcode": "Kode Akses",
|
||||
"webauthn_insert_key": "Masukkan kunci keamanan anda",
|
||||
"webauthn_sign_in": "Tekan tombol pada kunci keamanan Anda. Jika kunci keamanan Anda tidak memiliki tombol, masukkan kembali.",
|
||||
"webauthn_press_button": "Silakan tekan tombol pada kunci keamanan Anda…",
|
||||
"webauthn_press_button": "Silakan tekan tombol pada kunci keamanan Anda\u00e2\u20ac\u00a6",
|
||||
"webauthn_use_twofa": "Gunakan kode dua faktor dari telepon Anda",
|
||||
"webauthn_error": "Tidak dapat membaca kunci keamanan Anda.",
|
||||
"webauthn_unsupported_browser": "Browser Anda saat ini tidak mendukung WebAuthn.",
|
||||
@@ -90,7 +90,7 @@
|
||||
"copy_type_unsupported": "Tipe berkas ini tidak dapat disalin",
|
||||
"write": "Tulis",
|
||||
"preview": "Pratinjau",
|
||||
"loading": "Memuat…",
|
||||
"loading": "Memuat\u00e2\u20ac\u00a6",
|
||||
"error_title": "Gangguan",
|
||||
"error404": "Halaman yang akan kamu akses <strong>tidak dapat ditemukan</strong> atau <strong>kamu tidak memiliki akses </strong> untuk melihatnya.",
|
||||
"go_back": "Kembali",
|
||||
@@ -175,7 +175,7 @@
|
||||
"home.password_holder": "Kata Sandi",
|
||||
"home.switch_dashboard_context": "Alihkan Dasbor Konteks",
|
||||
"home.my_repos": "Repositori",
|
||||
"home.show_more_repos": "Tampilkan repositori lainnya…",
|
||||
"home.show_more_repos": "Tampilkan repositori lainnya\u00e2\u20ac\u00a6",
|
||||
"home.collaborative_repos": "Repositori Kolaboratif",
|
||||
"home.my_orgs": "Organisasi Saya",
|
||||
"home.my_mirrors": "Duplikat Saya",
|
||||
@@ -295,7 +295,7 @@
|
||||
"form.invalid_gpg_key": "Tidak dapat memverifikasi kunci GPG Anda: %s",
|
||||
"form.auth_failed": "Otentikasi gagal: %v",
|
||||
"form.target_branch_not_exist": "Target cabang tidak ada.",
|
||||
"user.change_avatar": "Ganti avatar anda…",
|
||||
"user.change_avatar": "Ganti avatar anda\u00e2\u20ac\u00a6",
|
||||
"user.repositories": "Repositori",
|
||||
"user.activity": "Aktivitas Publik",
|
||||
"user.followers": "Pengikut",
|
||||
@@ -564,18 +564,18 @@
|
||||
"repo.editor.fork_before_edit": "Anda harus mencabangkan repositori ini untuk membuat atau mengusulkan perubahan pada berkas ini.",
|
||||
"repo.editor.delete_this_file": "Hapus Berkas",
|
||||
"repo.editor.must_have_write_access": "Anda harus punya akses tulis untuk membuat atau mengusulkan perubahan pada berkas ini.",
|
||||
"repo.editor.name_your_file": "Nama berkas…",
|
||||
"repo.editor.name_your_file": "Nama berkas\u00e2\u20ac\u00a6",
|
||||
"repo.editor.filename_help": "Tambahkan direktori dengan mengetikkan nama direktori diikuti dengan garis miring ('/'). Hapus direktori dengan mengetikkan spasi balik pada awal bidang input.",
|
||||
"repo.editor.or": "atau",
|
||||
"repo.editor.cancel_lower": "Batalkan",
|
||||
"repo.editor.commit_changes": "Perubahan komitmen",
|
||||
"repo.editor.commit_message": "Commit message",
|
||||
"repo.editor.commit_message_desc": "Tambahkan deskripsi opsional yang panjang…",
|
||||
"repo.editor.commit_message_desc": "Tambahkan deskripsi opsional yang panjang\u00e2\u20ac\u00a6",
|
||||
"repo.editor.commit_directly_to_this_branch": "Komitmen langsung ke <strong class=\"branch-name\">%s</strong> cabang.",
|
||||
"repo.editor.create_new_branch": "Membuat <strong>new branch</strong> untuk tarik komit ini mulai permintaan.",
|
||||
"repo.editor.create_new_branch_np": "Buat <strong>cabang baru</strong> untuk komit ini.",
|
||||
"repo.editor.propose_file_change": "Usul perubahan berkas",
|
||||
"repo.editor.new_branch_name_desc": "Nama branch baru…",
|
||||
"repo.editor.new_branch_name_desc": "Nama branch baru\u00e2\u20ac\u00a6",
|
||||
"repo.editor.cancel": "Membatalkan",
|
||||
"repo.editor.no_changes_to_show": "Tidak ada perubahan untuk ditampilkan.",
|
||||
"repo.commits.commits": "Melakukan",
|
||||
@@ -816,7 +816,7 @@
|
||||
"repo.settings.title": "Judul",
|
||||
"repo.settings.deploy_key_content": "Konten",
|
||||
"repo.settings.branches": "Cabang",
|
||||
"repo.settings.choose_branch": "Pilih branch…",
|
||||
"repo.settings.choose_branch": "Pilih branch\u00e2\u20ac\u00a6",
|
||||
"repo.settings.tags": "Tag",
|
||||
"repo.diff.browse_source": "Telusuri Sumber",
|
||||
"repo.diff.parent": "orang tua",
|
||||
@@ -1315,24 +1315,24 @@
|
||||
"search.search": "Cari",
|
||||
"search.fuzzy_tooltip": "Include results that closely match the search term",
|
||||
"search.regexp": "Regexp",
|
||||
"search.repo_kind": "Search repos…",
|
||||
"search.user_kind": "Search users…",
|
||||
"search.org_kind": "Search orgs…",
|
||||
"search.team_kind": "Search teams…",
|
||||
"search.code_kind": "Search code…",
|
||||
"search.package_kind": "Search packages…",
|
||||
"search.project_kind": "Search projects…",
|
||||
"search.branch_kind": "Search branches…",
|
||||
"search.tag_kind": "Search tags…",
|
||||
"search.commit_kind": "Search commits…",
|
||||
"search.runner_kind": "Search runners…",
|
||||
"search.issue_kind": "Search issues…",
|
||||
"search.pull_kind": "Search pull requests…",
|
||||
"search.repo_kind": "Search repos\u2026",
|
||||
"search.user_kind": "Search users\u2026",
|
||||
"search.org_kind": "Search orgs\u2026",
|
||||
"search.team_kind": "Search teams\u2026",
|
||||
"search.code_kind": "Search code\u2026",
|
||||
"search.package_kind": "Search packages\u2026",
|
||||
"search.project_kind": "Search projects\u2026",
|
||||
"search.branch_kind": "Search branches\u2026",
|
||||
"search.tag_kind": "Search tags\u2026",
|
||||
"search.commit_kind": "Search commits\u2026",
|
||||
"search.runner_kind": "Search runners\u2026",
|
||||
"search.issue_kind": "Search issues\u2026",
|
||||
"search.pull_kind": "Search pull requests\u2026",
|
||||
"editor.buttons.strikethrough.tooltip": "Add strikethrough text",
|
||||
"filter.string.asc": "A–Z",
|
||||
"filter.string.desc": "Z–A",
|
||||
"filter.string.asc": "A\u2013Z",
|
||||
"filter.string.desc": "Z\u2013A",
|
||||
"install.install": "Installation",
|
||||
"install.installing_desc": "Installing now, please wait…",
|
||||
"install.installing_desc": "Installing now, please wait\u2026",
|
||||
"install.host": "Host",
|
||||
"install.db_schema": "Schema",
|
||||
"install.ssl_mode": "SSL",
|
||||
@@ -1409,7 +1409,7 @@
|
||||
"repo.readme": "README",
|
||||
"repo.mirror_address_url_invalid": "The provided URL is invalid. Make sure all components of the URL are escaped correctly.",
|
||||
"repo.mirror_lfs_endpoint_desc": "Sync will attempt to use the clone URL to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else.",
|
||||
"repo.adopt_search": "Enter username to search for unadopted repositories… (leave blank to find all)",
|
||||
"repo.adopt_search": "Enter username to search for unadopted repositories\u2026 (leave blank to find all)",
|
||||
"repo.desc.sha256": "SHA256",
|
||||
"repo.template.webhooks": "Webhooks",
|
||||
"repo.archive.title": "This repo is archived. You can view files and clone it. You cannot open issues or pull requests or push a commit.",
|
||||
@@ -1419,7 +1419,7 @@
|
||||
"repo.migrate_items_labels": "Labels",
|
||||
"repo.migrate.github_token_desc": "You can put one or more tokens here, separated by commas, to make migrating faster by circumventing the GitHub API rate limit. WARNING: Abusing this feature may violate the service provider's policy and may lead to getting your account(s) blocked.",
|
||||
"repo.migrate.permission_denied_blocked": "You cannot import from disallowed hosts. Please ask the admin to check ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings.",
|
||||
"repo.migrate.migrating": "Migrating from <b>%s</b>…",
|
||||
"repo.migrate.migrating": "Migrating from <b>%s</b>\u2026",
|
||||
"repo.migrate.codecommit.aws_access_key_id": "AWS Access Key ID",
|
||||
"repo.migrate.codecommit.aws_secret_access_key": "AWS Secret Access Key",
|
||||
"repo.migration_status": "Migration status",
|
||||
@@ -1468,13 +1468,13 @@
|
||||
"repo.issues.review.review": "Review",
|
||||
"repo.issues.assignee.error": "Not all assignees were added, due to an unexpected error.",
|
||||
"repo.compare.title": "Comparing changes",
|
||||
"repo.compare.description": "Choose two branches or tags to see what’s changed or to start a new pull request.",
|
||||
"repo.compare.description": "Choose two branches or tags to see what\u2019s changed or to start a new pull request.",
|
||||
"repo.pulls.new.description": "Discuss and review the changes in this comparison with others.",
|
||||
"repo.pulls.new.already_existed": "A pull request between these branches already exists",
|
||||
"repo.pulls.edit.already_changed": "Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes.",
|
||||
"repo.pulls.select_commit_hold_shift_for_range": "Select commit. Hold Shift and click to select a range.",
|
||||
"repo.pulls.nothing_to_compare_have_tag": "The selected branches/tags are equal.",
|
||||
"repo.pulls.is_checking": "Checking for merge conflicts…",
|
||||
"repo.pulls.is_checking": "Checking for merge conflicts\u2026",
|
||||
"repo.pulls.wrong_commit_id": "commit ID must be a commit ID on the target branch",
|
||||
"repo.pulls.no_merge_not_ready": "This pull request is not ready to be merged. Check review status and status checks.",
|
||||
"repo.pulls.rebase_merge_pull_request": "Rebase, then fast-forward",
|
||||
@@ -1492,7 +1492,7 @@
|
||||
"repo.pulls.status_checks_need_approvals_helper": "The workflow will only run after approval from the repository maintainer.",
|
||||
"repo.pulls.cmd_instruction_checkout_title": "Checkout",
|
||||
"repo.pulls.cmd_instruction_merge_warning": "Warning: This operation cannot merge pull request because \"autodetect manual merge\" is not enabled.",
|
||||
"repo.pulls.clear_merge_message_hint": "Clearing the merge message will only remove the commit message content and keep generated git trailers such as \"Co-Authored-By…\".",
|
||||
"repo.pulls.clear_merge_message_hint": "Clearing the merge message will only remove the commit message content and keep generated git trailers such as \"Co-Authored-By\u2026\".",
|
||||
"repo.signing.wont_sign.error": "There was an error while checking if the commit could be signed.",
|
||||
"repo.signing.wont_sign.twofa": "You must have two-factor authentication enabled to have commits signed.",
|
||||
"repo.wiki": "Wiki",
|
||||
@@ -1548,8 +1548,8 @@
|
||||
"repo.settings.visibility.private.bullet_two": "May remove the relationship between it and <strong>forks</strong>, <strong>watchers</strong>, and <strong>stars</strong>.",
|
||||
"repo.settings.unarchive.text": "Unarchiving the repo will restore its ability to receive commits and pushes, as well as new issues and pull requests.",
|
||||
"repo.settings.lfs": "LFS",
|
||||
"repo.settings.lfs_lock_path": "Filepath to lock…",
|
||||
"repo.settings.lfs_pointers.found": "Found %d blob pointer(s) — %d associated, %d unassociated (%d missing from store)",
|
||||
"repo.settings.lfs_lock_path": "Filepath to lock\u2026",
|
||||
"repo.settings.lfs_pointers.found": "Found %d blob pointer(s) \u2014 %d associated, %d unassociated (%d missing from store)",
|
||||
"repo.settings.lfs_pointers.sha": "Blob SHA",
|
||||
"repo.settings.lfs_pointers.oid": "OID",
|
||||
"repo.settings.pages": "Landing Page",
|
||||
@@ -1607,7 +1607,7 @@
|
||||
"repo.branch.commits_divergence_from": "Commit divergence: %[1]d behind and %[2]d ahead of %[3]s",
|
||||
"repo.branch.commits_no_divergence": "The same as branch %[1]s",
|
||||
"repo.find_file.follow_symlink": "Follow this symlink to where it is pointing at",
|
||||
"graphs.component_loading": "Loading %s…",
|
||||
"graphs.component_loading": "Loading %s\u2026",
|
||||
"org.pinned_repos": "Featured Projects",
|
||||
"org.public_members": "Public Members",
|
||||
"org.view_all_members": "View all %d members",
|
||||
@@ -1615,7 +1615,7 @@
|
||||
"org.settings.email": "Contact Email Address",
|
||||
"org.settings.change_visibility": "Change Visibility",
|
||||
"org.settings.change_visibility_notices_1": "If the organization is converted to private, the repository stars will be removed and cannot be restored.",
|
||||
"org.settings.change_visibility_notices_2": "Non-members will lose access to the organization’s repositories if visibility is changed to private.",
|
||||
"org.settings.change_visibility_notices_2": "Non-members will lose access to the organization\u2019s repositories if visibility is changed to private.",
|
||||
"org.settings.change_visibility_success": "The visibility of organization %s has been successfully changed.",
|
||||
"org.settings.visibility_desc": "Change who can view the organization and its repositories.",
|
||||
"org.settings.rename": "Rename Organization",
|
||||
@@ -1670,11 +1670,11 @@
|
||||
"admin.auths.port": "Port",
|
||||
"admin.auths.ssh_keys_are_verified": "SSH keys in LDAP are considered as verified",
|
||||
"admin.auths.helo_hostname": "HELO Hostname",
|
||||
"admin.auths.oauth2_full_name_claim_name": "Full Name Claim Name. (Optional — if set, the user's full name will always be synchronized with this claim)",
|
||||
"admin.auths.oauth2_full_name_claim_name": "Full Name Claim Name. (Optional \u2014 if set, the user's full name will always be synchronized with this claim)",
|
||||
"admin.auths.oauth2_ssh_public_key_claim_name": "SSH Public Key Claim Name",
|
||||
"admin.auths.oauth2_admin_group": "Group Claim value for administrator users. (Optional — requires claim name above)",
|
||||
"admin.auths.oauth2_restricted_group": "Group Claim value for restricted users. (Optional — requires claim name above)",
|
||||
"admin.auths.oauth2_map_group_to_team": "Map claimed groups to Organization teams. (Optional — requires claim name above)",
|
||||
"admin.auths.oauth2_admin_group": "Group Claim value for administrator users. (Optional \u2014 requires claim name above)",
|
||||
"admin.auths.oauth2_restricted_group": "Group Claim value for restricted users. (Optional \u2014 requires claim name above)",
|
||||
"admin.auths.oauth2_map_group_to_team": "Map claimed groups to Organization teams. (Optional \u2014 requires claim name above)",
|
||||
"admin.auths.sspi_auto_create_users_helper": "Allow SSPI auth method to automatically create new accounts for users that log in for the first time",
|
||||
"admin.auths.sspi_strip_domain_names_helper": "If checked, domain names will be removed from logon names (e.g. \"DOMAIN\\user\" and \"user@example.org\" both will become just \"user\").",
|
||||
"admin.auths.sspi_separator_replacement_helper": "The character to use to replace the separators of down-level logon names (e.g. the \\ in \"DOMAIN\\user\") and user principal names (e.g. the @ in \"user@example.org\").",
|
||||
@@ -1784,7 +1784,7 @@
|
||||
"actions.general.collaborative_owner_not_exist": "The collaborative owner does not exist.",
|
||||
"actions.general.remove_collaborative_owner": "Remove Collaborative Owner",
|
||||
"actions.general.remove_collaborative_owner_desc": "Removing a collaborative owner will prevent the repositories of the owner from accessing the actions in this repository. Continue?",
|
||||
"git.filemode.changed_filemode": "%[1]s → %[2]s",
|
||||
"git.filemode.changed_filemode": "%[1]s \u2192 %[2]s",
|
||||
"org.pinned_repos_empty_title": "Showcase your best work",
|
||||
"org.pinned_repos_empty_desc": "Pin up to 6 repositories to highlight your organization's most important projects.",
|
||||
"org.settings.pinned.manage": "Manage Pins",
|
||||
@@ -1954,6 +1954,12 @@
|
||||
"repo.settings.pages.nav_show_repository": "Tampilkan tautan repositori (tombol Lihat kode sumber)",
|
||||
"repo.settings.pages.nav_show_releases": "Tampilkan tautan rilis",
|
||||
"repo.settings.pages.nav_show_issues": "Tampilkan tautan isu",
|
||||
"repo.settings.pages.section_labels": "Label bagian",
|
||||
"repo.settings.pages.section_labels_desc": "Sesuaikan judul yang ditampilkan di halaman arahan untuk setiap bagian",
|
||||
"repo.settings.pages.label_value_props": "Judul proposisi nilai",
|
||||
"repo.settings.pages.label_value_props_help": "Judul untuk bagian proposisi nilai (mis., \"Mengapa memilih kami\")",
|
||||
"repo.settings.pages.label_features": "Judul fitur",
|
||||
"repo.settings.pages.label_features_help": "Judul untuk bagian fitur (mis., \"Kemampuan\")",
|
||||
"repo.settings.pages.blog_section": "Bagian blog",
|
||||
"repo.settings.pages.blog_enabled_desc": "Tampilkan postingan blog terbaru di halaman arahan",
|
||||
"repo.settings.pages.blog_headline": "Judul blog",
|
||||
@@ -2311,5 +2317,68 @@
|
||||
"repo.view_file": "View File",
|
||||
"actions.runners.waiting_jobs": "Pekerjaan Menunggu",
|
||||
"actions.runners.back_to_runners": "Kembali ke Runner",
|
||||
"actions.runners.no_waiting_jobs": "Tidak ada pekerjaan yang menunggu untuk label ini"
|
||||
}
|
||||
"actions.runners.no_waiting_jobs": "Tidak ada pekerjaan yang menunggu untuk label ini",
|
||||
"repo.settings.pages.cross_promote_section": "Bagian promosi silang",
|
||||
"repo.settings.pages.cross_promote_enabled_desc": "Tampilkan repositori promosi silang di halaman arahan",
|
||||
"repo.settings.pages.cross_promote_headline": "Judul bagian",
|
||||
"repo.settings.pages.cross_promote_subheadline": "Subjudul bagian",
|
||||
"repo.settings.pages.cross_promote_help": "Konfigurasikan repositori di Pengaturan > Promosi Silang.",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files": "Kecualikan file dan folder tersembunyi",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files_desc": "Jangan dorong file/folder yang dimulai dengan \".\" atau ditandai sebagai tersembunyi ke mirror jarak jauh",
|
||||
"repo.settings.pages.value_props_headline": "Judul bagian",
|
||||
"repo.settings.pages.value_props_headline_help": "Judul besar untuk bagian proposisi nilai. Label kecil di atasnya diatur di Label bagian.",
|
||||
"repo.settings.pages.value_props_subheadline": "Subjudul bagian",
|
||||
"repo.settings.pages.features_headline": "Judul bagian",
|
||||
"repo.settings.pages.features_headline_help": "Judul besar untuk bagian fitur. Label kecil di atasnya diatur di Label bagian.",
|
||||
"repo.settings.pages.features_subheadline": "Subjudul bagian",
|
||||
"repo.settings.license_acl_fields": "Pengaturan Auditable Commercial License",
|
||||
"repo.settings.license_acl_fields_desc": "Field-field ini diperlukan untuk menyesuaikan Auditable Commercial License (ACL) v1.0 untuk proyek Anda.",
|
||||
"repo.settings.license_acl_jurisdiction": "Yurisdiksi",
|
||||
"repo.settings.license_acl_jurisdiction_help": "Hukum yang berlaku untuk Bagian 8.1.",
|
||||
"repo.settings.license_acl_venue": "Tempat",
|
||||
"repo.settings.license_acl_venue_help": "Tempat pengadilan untuk sengketa.",
|
||||
"repo.settings.license_acl_contact": "Alamat kontak (opsional)",
|
||||
"repo.settings.license_acl_contact_help": "Ke mana pemberitahuan hukum harus dikirim.",
|
||||
"repo.settings.license_acl_license_url": "URL lisensi (opsional)",
|
||||
"repo.settings.license_acl_tier_url": "URL jadwal tingkatan (opsional)",
|
||||
"repo.settings.license_acl_required": "Yurisdiksi dan Tempat diperlukan untuk Auditable Commercial License.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_tooltip": "Reset mirror ke commit tertentu",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_title": "Reset push mirror ke commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_title": "Tindakan destruktif",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_body": "Ini akan force-push commit terpilih ke remote dan menulis ulang riwayat branch di sana. Auto-sync akan dijeda.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch": "Branch di remote",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch_help": "Branch di mirror remote yang akan direset.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit": "SHA commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit_help": "SHA lengkap dari commit di repositori ini untuk dipush ke remote.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_confirm": "Force-push dan jeda sinkronisasi",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_required": "Branch dan SHA commit diperlukan.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_failed": "Gagal mereset mirror: %s",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_success": "Reset mirror selesai. Auto-sync telah dijeda.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button": "Reset semua mirror ke commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button_help": "Force-push commit yang sama ke setiap push mirror sekaligus dan jeda semua sinkronisasi otomatis.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_title": "Reset semua push mirror ke commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_title": "Tindakan destruktif \u2014 memengaruhi semua mirror",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_body": "Ini akan force-push commit terpilih ke branch bernama di setiap push mirror.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_confirm": "Force-push semua mirror dan jeda sinkronisasi",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_success": "Reset selesai. %d mirror diperbarui.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_partial": "Reset sebagian: %d berhasil, %d gagal. Kesalahan: %s",
|
||||
"repo.settings.repo_reset.title": "Reset riwayat repositori",
|
||||
"repo.settings.repo_reset.desc": "Memindahkan HEAD branch ke commit tertentu dan force-push ke semua mirror.",
|
||||
"repo.settings.repo_reset.button": "Reset ke commit",
|
||||
"repo.settings.repo_reset.warning_title": "Ini menulis ulang riwayat repositori",
|
||||
"repo.settings.repo_reset.warning_body": "Tindakan ini akan:",
|
||||
"repo.settings.repo_reset.warning_branch": "Memindahkan HEAD branch di server ini ke commit terpilih.",
|
||||
"repo.settings.repo_reset.warning_collaborators": "Siapa pun yang telah meng-clone repositori harus meng-clone ulang.",
|
||||
"repo.settings.repo_reset.warning_mirrors": "Semua push mirror akan dipaksa diperbarui dan sinkronisasi otomatisnya akan dijeda.",
|
||||
"repo.settings.repo_reset.branch": "Branch untuk direset",
|
||||
"repo.settings.repo_reset.commit": "SHA commit",
|
||||
"repo.settings.repo_reset.confirm_name": "Untuk konfirmasi, ketik nama repositori: %s",
|
||||
"repo.settings.repo_reset.confirm": "Reset repositori",
|
||||
"repo.settings.repo_reset.required": "Branch dan SHA commit diperlukan.",
|
||||
"repo.settings.repo_reset.confirm_mismatch": "Konfirmasi nama repositori tidak cocok.",
|
||||
"repo.settings.repo_reset.commit_not_found": "Commit %s tidak ditemukan.",
|
||||
"repo.settings.repo_reset.branch_not_found": "Branch %s tidak ada.",
|
||||
"repo.settings.repo_reset.failed": "Reset gagal: %s",
|
||||
"repo.settings.repo_reset.success": "Reset selesai.",
|
||||
"repo.settings.repo_reset.partial": "Repositori direset secara lokal, tetapi beberapa mirror gagal: %s"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@
|
||||
"language": "Taal",
|
||||
"notifications": "Meldingen",
|
||||
"active_stopwatch": "Actieve Tijd Tracker",
|
||||
"create_new": "Maken…",
|
||||
"user_profile_and_more": "Profiel en instellingen…",
|
||||
"create_new": "Maken\u00e2\u20ac\u00a6",
|
||||
"user_profile_and_more": "Profiel en instellingen\u00e2\u20ac\u00a6",
|
||||
"signed_in_as": "Aangemeld als",
|
||||
"toc": "Inhoudsopgave",
|
||||
"licenses": "Licenties",
|
||||
@@ -30,7 +30,7 @@
|
||||
"passcode": "PIN",
|
||||
"webauthn_insert_key": "Voer uw beveiligingssleutel in",
|
||||
"webauthn_sign_in": "Druk op de knop van uw beveiligingssleutel. Als uw beveiligingssleutel geen knop heeft, voeg deze dan opnieuw in.",
|
||||
"webauthn_press_button": "Druk alstublieft op de knop van uw beveiligingssleutel…",
|
||||
"webauthn_press_button": "Druk alstublieft op de knop van uw beveiligingssleutel\u00e2\u20ac\u00a6",
|
||||
"webauthn_use_twofa": "Gebruik een twee-factor code van uw telefoon",
|
||||
"webauthn_error": "Kon uw beveiligingssleutel niet lezen.",
|
||||
"webauthn_unsupported_browser": "Uw browser ondersteunt momenteel geen WebAuthn.",
|
||||
@@ -73,11 +73,11 @@
|
||||
"edit": "Bewerken",
|
||||
"enabled": "Ingeschakeld",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"copy": "Kopiëren",
|
||||
"copy": "Kopi\u00ebren",
|
||||
"copy_url": "Kopieer URL",
|
||||
"copy_branch": "Kopieer branchnaam",
|
||||
"copy_success": "Gekopieerd!",
|
||||
"copy_error": "Kopiëren mislukt",
|
||||
"copy_error": "Kopi\u00c3\u00abren mislukt",
|
||||
"write": "Schrijven",
|
||||
"preview": "Voorbeeld",
|
||||
"loading": "Laden...",
|
||||
@@ -96,12 +96,12 @@
|
||||
"error.occurred": "Er is een fout opgetreden",
|
||||
"error.not_found": "Het doel kon niet worden gevonden.",
|
||||
"error.network_error": "Netwerk fout",
|
||||
"startpage.app_desc": "Geïntegreerd in je workflow",
|
||||
"startpage.app_desc": "Ge\u00c3\u00afntegreerd in je workflow",
|
||||
"startpage.install": "Overal Implementeren",
|
||||
"startpage.lightweight": "Razendsnel",
|
||||
"startpage.lightweight_desc": "Minimale voetafdruk, maximale prestaties. GitCaddy draait efficiënt op alles, van Raspberry Pi tot enterprise servers.",
|
||||
"startpage.lightweight_desc": "Minimale voetafdruk, maximale prestaties. GitCaddy draait effici\u00c3\u00abnt op alles, van Raspberry Pi tot enterprise servers.",
|
||||
"install.install": "Installatie",
|
||||
"install.title": "Initiële configuratie",
|
||||
"install.title": "Initi\u00c3\u00able configuratie",
|
||||
"install.docker_helper": "Als je GitCaddy draait in Docker, Lees eerst de <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">documentatie</a> voordat je een instelling aanpast.",
|
||||
"install.require_db_desc": "GitCaddy vereist MySQL, PostgreSQL, MSSQL, SQLite3 of TiDB (MySQL protocol).",
|
||||
"install.db_title": "Database-instellingen",
|
||||
@@ -162,7 +162,7 @@
|
||||
"install.enable_captcha": "Registratie CAPTCHA inschakelen",
|
||||
"install.enable_captcha_popup": "Vereis captcha validatie voor zelf-registratie van gebruiker.",
|
||||
"install.require_sign_in_view": "Vereis inloggen om pagina's te kunnen bekijken",
|
||||
"install.admin_setting_desc": "Het creëren van een administrator-account is optioneel. De eerste geregistreerde gebruiker wordt automatisch de beheerder.",
|
||||
"install.admin_setting_desc": "Het cre\u00c3\u00abren van een administrator-account is optioneel. De eerste geregistreerde gebruiker wordt automatisch de beheerder.",
|
||||
"install.admin_title": "Instellingen beheerdersaccount",
|
||||
"install.admin_name": "Admin gebruikersnaam",
|
||||
"install.admin_password": "Wachtwoord",
|
||||
@@ -170,7 +170,7 @@
|
||||
"install.admin_email": "E-mailadres",
|
||||
"install.install_btn_confirm": "Installeer GitCaddy",
|
||||
"install.test_git_failed": "Git test niet gelukt: 'git' commando %v",
|
||||
"install.sqlite3_not_available": "Deze GitCaddy-versie biedt geen ondersteuning voor SQLite3. Download de officiële build van %s (niet de versie van de 'gobuild').",
|
||||
"install.sqlite3_not_available": "Deze GitCaddy-versie biedt geen ondersteuning voor SQLite3. Download de offici\u00c3\u00able build van %s (niet de versie van de 'gobuild').",
|
||||
"install.invalid_db_setting": "De database instelling zijn niet correct: %v",
|
||||
"install.invalid_repo_path": "Het pad van de hoofdmap van de repository is ongeldig: %v",
|
||||
"install.invalid_app_data_path": "Ongeldig app-gegevenspad: %v",
|
||||
@@ -192,7 +192,7 @@
|
||||
"home.uname_holder": "Gebruikersnaam of e-mailadres",
|
||||
"home.password_holder": "Wachtwoord",
|
||||
"home.switch_dashboard_context": "Wissel voorpaginacontext",
|
||||
"home.show_more_repos": "Toon meer repositories…",
|
||||
"home.show_more_repos": "Toon meer repositories\u00e2\u20ac\u00a6",
|
||||
"home.collaborative_repos": "Gedeelde repositories",
|
||||
"home.my_orgs": "Mijn organisaties",
|
||||
"home.my_mirrors": "Mijn spiegels",
|
||||
@@ -204,16 +204,16 @@
|
||||
"home.show_both_archived_unarchived": "Toont zowel gearchiveerd als niet-gearchiveerd",
|
||||
"home.show_only_archived": "Toon alleen gearchiveerd",
|
||||
"home.show_only_unarchived": "Toon alleen niet gearchiveerd",
|
||||
"home.show_private": "Privé",
|
||||
"home.show_both_private_public": "Toon zowel openbaar als privé",
|
||||
"home.show_only_private": "Toon alleen privé",
|
||||
"home.show_private": "Priv\u00c3\u00a9",
|
||||
"home.show_both_private_public": "Toon zowel openbaar als priv\u00c3\u00a9",
|
||||
"home.show_only_private": "Toon alleen priv\u00c3\u00a9",
|
||||
"home.show_only_public": "Toon alleen opbenbaar",
|
||||
"home.issues.in_your_repos": "In uw repositories",
|
||||
"explore.users": "Gebruikers",
|
||||
"explore.organizations": "Organisaties",
|
||||
"explore.packages": "Pakketten",
|
||||
"explore.packages.empty.description": "Er zijn nog geen openbare of globale pakketten beschikbaar.",
|
||||
"explore.code_last_indexed_at": "Laatst geïndexeerd %s",
|
||||
"explore.code_last_indexed_at": "Laatst ge\u00c3\u00afndexeerd %s",
|
||||
"auth.create_new_account": "Account registreren",
|
||||
"auth.disable_register_prompt": "Registratie is uitgeschakeld. Neem alstublieft contact op met de pagina beheerder.",
|
||||
"auth.disable_register_mail": "E-mailbevestiging voor registratie is uitgeschakeld.",
|
||||
@@ -236,7 +236,7 @@
|
||||
"auth.reset_password_helper": "Account herstellen",
|
||||
"auth.password_too_short": "De lengte van uw wachtwoord moet tenminste %d karakters zijn.",
|
||||
"auth.non_local_account": "Non-lokale gebruikers mogen hun wachtwoord niet wijzigen via de webinterface.",
|
||||
"auth.verify": "Verifiëren",
|
||||
"auth.verify": "Verifi\u00c3\u00abren",
|
||||
"auth.scratch_code": "Eenmalige code",
|
||||
"auth.use_scratch_code": "Gebruik een eenmalige code",
|
||||
"auth.twofa_scratch_used": "Je hebt je eenmalige code gebruikt. Je wordt omgeleid naar de tweeledige-authenticatie instellingen pagina zodat je de inschrijving van het apparaat kan verwijderen of een nieuwe eenmalige code kan genereren.",
|
||||
@@ -265,7 +265,7 @@
|
||||
"auth.sspi_auth_failed": "SSPI-authenticatie mislukt",
|
||||
"auth.password_pwned_err": "Kan het verzoek om HaveIBeenPwned niet voltooien",
|
||||
"mail.view_it_on": "Bekijk het op %s",
|
||||
"mail.link_not_working_do_paste": "Werkt dit niet? Probeer het te kopiëren en te plakken naar uw browser.",
|
||||
"mail.link_not_working_do_paste": "Werkt dit niet? Probeer het te kopi\u00c3\u00abren en te plakken naar uw browser.",
|
||||
"mail.hi_user_x": "Hoi <b>%s</b>,",
|
||||
"mail.activate_account": "Activeer uw account",
|
||||
"mail.activate_account.title": "%s, activeer alstublieft uw account",
|
||||
@@ -344,36 +344,36 @@
|
||||
"form.username_been_taken": "Deze naam is al in gebruik.",
|
||||
"form.username_change_not_local_user": "Niet-lokale gebruikers mogen hun gebruikersnaam niet wijzigen.",
|
||||
"form.repo_name_been_taken": "De repository-naam wordt al gebruikt.",
|
||||
"form.repository_force_private": "Forceer privé is ingeschakeld: privé repositories kunnen niet openbaar worden gemaakt.",
|
||||
"form.repository_force_private": "Forceer priv\u00c3\u00a9 is ingeschakeld: priv\u00c3\u00a9 repositories kunnen niet openbaar worden gemaakt.",
|
||||
"form.repository_files_already_exist": "Er bestaan al bestanden voor deze repository. Neem contact op met de systeembeheerder.",
|
||||
"form.repository_files_already_exist.delete": "Er bestaan al bestanden voor deze repository. U moet deze verwijderen.",
|
||||
"form.repository_files_already_exist.adopt_or_delete": "Er bestaan al bestanden voor deze repository. Adopteer of verwijder deze.",
|
||||
"form.visit_rate_limit": "Bezoeklimiet op afstand gerichter.",
|
||||
"form.org_name_been_taken": "Naam van de organisatie wordt al gebruikt.",
|
||||
"form.team_name_been_taken": "De teamnaam is al in gebruik.",
|
||||
"form.team_no_units_error": "Toegang verlenen tot ten minste één repository sectie.",
|
||||
"form.team_no_units_error": "Toegang verlenen tot ten minste \u00c3\u00a9\u00c3\u00a9n repository sectie.",
|
||||
"form.email_been_used": "Het emailadres is al in gebruik.",
|
||||
"form.email_invalid": "Het e-mailadres is ongeldig.",
|
||||
"form.username_password_incorrect": "Gebruikersnaam of wachtwoord is onjuist.",
|
||||
"form.password_complexity": "Wachtwoord voldoet niet aan complexiteit eisen:",
|
||||
"form.password_lowercase_one": "Minstens één kleine letter",
|
||||
"form.password_uppercase_one": "Minstens één hoofdletter",
|
||||
"form.password_digit_one": "Minstens één cijfer",
|
||||
"form.password_special_one": "Minstens één speciaal teken (interpunctie, haakjes, aanhalingstekens, etc.)",
|
||||
"form.password_lowercase_one": "Minstens \u00c3\u00a9\u00c3\u00a9n kleine letter",
|
||||
"form.password_uppercase_one": "Minstens \u00c3\u00a9\u00c3\u00a9n hoofdletter",
|
||||
"form.password_digit_one": "Minstens \u00c3\u00a9\u00c3\u00a9n cijfer",
|
||||
"form.password_special_one": "Minstens \u00c3\u00a9\u00c3\u00a9n speciaal teken (interpunctie, haakjes, aanhalingstekens, etc.)",
|
||||
"form.enterred_invalid_repo_name": "De repository-naam die u hebt ingevoerd is niet correct.",
|
||||
"form.enterred_invalid_org_name": "De organizatienaam die u hebt ingevoerd is niet correct.",
|
||||
"form.enterred_invalid_owner_name": "De nieuwe eigenaarnaam is niet geldig.",
|
||||
"form.enterred_invalid_password": "Het ingevoerde wachtwoord is onjuist.",
|
||||
"form.user_not_exist": "De gebruiker bestaat niet.",
|
||||
"form.team_not_exist": "Dit team bestaat niet.",
|
||||
"form.last_org_owner": "Je kunt de laatste eigenaar van een organisatie niet verwijderen. Er moet er minimaal één eigenaar in een organisatie zitten.",
|
||||
"form.last_org_owner": "Je kunt de laatste eigenaar van een organisatie niet verwijderen. Er moet er minimaal \u00c3\u00a9\u00c3\u00a9n eigenaar in een organisatie zitten.",
|
||||
"form.cannot_add_org_to_team": "Een organisatie kan niet worden toegevoegd als een teamlid.",
|
||||
"form.invalid_ssh_key": "Kan de SSH-sleutel niet verifiëren: %s",
|
||||
"form.invalid_gpg_key": "Kan de GPG-sleutel niet verifiëren: %s",
|
||||
"form.invalid_ssh_key": "Kan de SSH-sleutel niet verifi\u00c3\u00abren: %s",
|
||||
"form.invalid_gpg_key": "Kan de GPG-sleutel niet verifi\u00c3\u00abren: %s",
|
||||
"form.invalid_ssh_principal": "Ongeldige verantwoordelijke: %s",
|
||||
"form.auth_failed": "Verificatie mislukt: %v",
|
||||
"form.target_branch_not_exist": "Doel branch bestaat niet",
|
||||
"user.change_avatar": "Wijzig je profielfoto…",
|
||||
"user.change_avatar": "Wijzig je profielfoto\u00e2\u20ac\u00a6",
|
||||
"user.repositories": "Repository's",
|
||||
"user.activity": "Activiteit",
|
||||
"user.followers": "Volgers",
|
||||
@@ -430,7 +430,7 @@
|
||||
"settings.enable_custom_avatar": "Aangepaste avatar inschakelen",
|
||||
"settings.choose_new_avatar": "Kies een nieuwe avatar",
|
||||
"settings.delete_current_avatar": "Verwijder huidige avatar",
|
||||
"settings.uploaded_avatar_not_a_image": "Het geüploade bestand is geen afbeelding.",
|
||||
"settings.uploaded_avatar_not_a_image": "Het ge\u00c3\u00bcploade bestand is geen afbeelding.",
|
||||
"settings.update_avatar_success": "Je avatar is bijgewerkt.",
|
||||
"settings.update_user_avatar_success": "De avatar van de gebruiker is bijgewerkt.",
|
||||
"settings.change_password": "Wachtwoord bijwerken",
|
||||
@@ -473,7 +473,7 @@
|
||||
"settings.add_key": "Sleutel toevoegen",
|
||||
"settings.ssh_desc": "Deze publieke SSH sleutels worden geassocieerd met uw account. De bijbehorende private sleutels geven volledige toegang toe tot je repositories.",
|
||||
"settings.principal_desc": "Deze SSH-certificaatverantwoordelijken zijn gekoppeld aan uw account en geven volledige toegang tot uw repositories.",
|
||||
"settings.gpg_desc": "Deze publieke GPG-sleutels zijn verbonden met je account. Houd je privé-sleutels veilig, omdat hiermee commits kunnen worden ondertekend.",
|
||||
"settings.gpg_desc": "Deze publieke GPG-sleutels zijn verbonden met je account. Houd je priv\u00c3\u00a9-sleutels veilig, omdat hiermee commits kunnen worden ondertekend.",
|
||||
"settings.ssh_helper": "<strong>Weet u niet hoe?</strong> Lees dan onze handleiding voor het <a href=\"%s\"> genereren van SSH sleutels</a> of voor <a href=\"%s\"> algemene SSH</a> problemen.",
|
||||
"settings.gpg_helper": "<strong>Hulp nodig?</strong> Neem een kijkje op de GitHub handleiding <a href=\"%s\">over GPG</a>.",
|
||||
"settings.add_new_key": "SSH sleutel toevoegen",
|
||||
@@ -489,15 +489,15 @@
|
||||
"settings.gpg_key_matched_identities": "Overeenkomende identiteiten:",
|
||||
"settings.gpg_key_matched_identities_long": "De ingesloten identiteiten in deze sleutel komen overeen met de geactiveerde e-mailadressen voor deze gebruiker. Commits die overeenkomen met deze e-mailadressen kunnen worden geverifieerd met deze sleutel.",
|
||||
"settings.gpg_key_verified": "Geverifieerde sleutel",
|
||||
"settings.gpg_key_verified_long": "Sleutel is geverifieerd met een token en kan worden gebruikt om commits te verifiëren die overeenkomen met alle geactiveerde e-mailadressen voor deze gebruiker naast de bijbehorende identiteiten voor deze sleutel.",
|
||||
"settings.gpg_key_verify": "Verifiëren",
|
||||
"settings.gpg_key_verified_long": "Sleutel is geverifieerd met een token en kan worden gebruikt om commits te verifi\u00c3\u00abren die overeenkomen met alle geactiveerde e-mailadressen voor deze gebruiker naast de bijbehorende identiteiten voor deze sleutel.",
|
||||
"settings.gpg_key_verify": "Verifi\u00c3\u00abren",
|
||||
"settings.gpg_token_required": "U moet een handtekening opgeven voor de onderstaande token",
|
||||
"settings.gpg_token_help": "U kunt een handtekening genereren met:",
|
||||
"settings.gpg_token_signature": "Gepantserde GPG-handtekening",
|
||||
"settings.key_signature_gpg_placeholder": "Begint met '-----BEGIN PGP SIGNATURE-----'",
|
||||
"settings.ssh_key_verified": "Geverifieerde sleutel",
|
||||
"settings.ssh_key_verified_long": "Sleutel is geverifieerd met een token en kan worden gebruikt om commits te verifiëren die overeenkomen met alle geactiveerde e-mailadressen voor deze gebruiker.",
|
||||
"settings.ssh_key_verify": "Verifiëren",
|
||||
"settings.ssh_key_verified_long": "Sleutel is geverifieerd met een token en kan worden gebruikt om commits te verifi\u00c3\u00abren die overeenkomen met alle geactiveerde e-mailadressen voor deze gebruiker.",
|
||||
"settings.ssh_key_verify": "Verifi\u00c3\u00abren",
|
||||
"settings.ssh_token_required": "U moet een handtekening opgeven voor het onderstaande token",
|
||||
"settings.ssh_token_help": "U kunt een handtekening genereren door het volgende:",
|
||||
"settings.ssh_token_signature": "Gepantserde SSH handtekening",
|
||||
@@ -597,7 +597,7 @@
|
||||
"settings.visibility": "Gebruiker zichtbaarheid",
|
||||
"settings.visibility.public": "Openbaar",
|
||||
"settings.visibility.limited": "Beperkt",
|
||||
"settings.visibility.private": "Privé",
|
||||
"settings.visibility.private": "Priv\u00c3\u00a9",
|
||||
"repo.owner": "Eigenaar",
|
||||
"repo.owner_helper": "Sommige organisaties kunnen niet worden weergegeven in de dropdown vanwege een limiet op het maximale aantal repositories.",
|
||||
"repo.repo_name": "Naam van repository",
|
||||
@@ -608,7 +608,7 @@
|
||||
"repo.template_description": "Sjabloon repositories laten gebruikers nieuwe repositories genereren met dezelfde directory structuur, bestanden en optionele instellingen.",
|
||||
"repo.visibility": "Zichtbaarheid",
|
||||
"repo.visibility_description": "Alleen de eigenaar of de organisatielid kan het zien als ze rechten hebben.",
|
||||
"repo.visibility_helper_forced": "De sitebeheerder verplicht alle repositories om privé te zijn.",
|
||||
"repo.visibility_helper_forced": "De sitebeheerder verplicht alle repositories om priv\u00c3\u00a9 te zijn.",
|
||||
"repo.visibility_fork_helper": "(Verandering van deze waarde zal van invloed zijn op alle forks)",
|
||||
"repo.clone_helper": "Heb je hulp nodig om te clonen? Bekijk dan de <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">handleiding</a>.",
|
||||
"repo.fork_repo": "Repository forken",
|
||||
@@ -671,18 +671,18 @@
|
||||
"repo.blame_prior": "Bekijk de schuld voorafgaand aan deze verandering",
|
||||
"repo.transfer.accept": "Accepteer overdracht",
|
||||
"repo.transfer.reject": "Overdracht afwijzen",
|
||||
"repo.desc.private": "Privé",
|
||||
"repo.desc.private": "Priv\u00c3\u00a9",
|
||||
"repo.desc.public": "Openbaar",
|
||||
"repo.desc.template": "Sjabloon",
|
||||
"repo.desc.internal": "Interne",
|
||||
"repo.desc.archived": "Gearchiveerd",
|
||||
"repo.template.items": "Sjabloon items",
|
||||
"repo.template.git_content": "Git inhoud (standaard Branch)",
|
||||
"repo.template.git_hooks_tooltip": "Je bent momenteel niet in staat om Git Hooks één keer te wijzigen of te verwijderen. Selecteer deze optie alleen als je de sjabloonrepository vertrouwt.",
|
||||
"repo.template.git_hooks_tooltip": "Je bent momenteel niet in staat om Git Hooks \u00c3\u00a9\u00c3\u00a9n keer te wijzigen of te verwijderen. Selecteer deze optie alleen als je de sjabloonrepository vertrouwt.",
|
||||
"repo.template.topics": "Onderwerpen",
|
||||
"repo.template.avatar": "Profielfoto",
|
||||
"repo.template.issue_labels": "Issuelabels",
|
||||
"repo.template.one_item": "Moet ten minste één sjabloon selecteren",
|
||||
"repo.template.one_item": "Moet ten minste \u00c3\u00a9\u00c3\u00a9n sjabloon selecteren",
|
||||
"repo.template.invalid": "Moet een sjabloon repository selecteren",
|
||||
"repo.archive.issue.nocomment": "Deze repo is gearchiveerd. U kunt niet reageren op problemen.",
|
||||
"repo.archive.pull.nocomment": "Deze repo is gearchiveerd. U kunt niet reageren op pull requests.",
|
||||
@@ -773,7 +773,7 @@
|
||||
"repo.invisible_runes_line": "Deze lijn heeft onzichtbare unicode karakters",
|
||||
"repo.ambiguous_runes_line": "Deze lijn heeft dubbelzinnige unicode karakters",
|
||||
"repo.unescape_control_characters": "Onescape",
|
||||
"repo.file_copy_permalink": "Permalink kopiëren",
|
||||
"repo.file_copy_permalink": "Permalink kopi\u00c3\u00abren",
|
||||
"repo.view_git_blame": "Bekijk Git Blame",
|
||||
"repo.video_not_supported_in_browser": "Je browser ondersteunt de HTML5 'video'-tag niet.",
|
||||
"repo.audio_not_supported_in_browser": "Je browser ondersteunt de HTML5 'audio'-tag niet.",
|
||||
@@ -799,7 +799,7 @@
|
||||
"repo.editor.fork_before_edit": "Je moet deze repository forken om veranderingen te maken of voor te stellen.",
|
||||
"repo.editor.delete_this_file": "Verwijder bestand",
|
||||
"repo.editor.must_have_write_access": "U moet schrijftoegang hebben om aanpassingen te maken of voor te stellen in dit bestand.",
|
||||
"repo.editor.name_your_file": "Bestandsnaam…",
|
||||
"repo.editor.name_your_file": "Bestandsnaam\u00e2\u20ac\u00a6",
|
||||
"repo.editor.filename_help": "Voeg een map toe door zijn naam te typen, gevolgd door een slash ('/'). Verwijder een map door op backspace te drukken aan het begin van het tekstveld.",
|
||||
"repo.editor.or": "of",
|
||||
"repo.editor.cancel_lower": "Annuleer",
|
||||
@@ -809,13 +809,13 @@
|
||||
"repo.editor.patching": "Patchen:",
|
||||
"repo.editor.new_patch": "Nieuwe Patch",
|
||||
"repo.editor.commit_message": "Commit-bericht",
|
||||
"repo.editor.commit_message_desc": "Voeg een optionele uitgebreide omschrijving toe…",
|
||||
"repo.editor.commit_message_desc": "Voeg een optionele uitgebreide omschrijving toe\u00e2\u20ac\u00a6",
|
||||
"repo.editor.signoff_desc": "Voeg een Signed-off-by toe aan het einde van het commit logbericht.",
|
||||
"repo.editor.commit_directly_to_this_branch": "Commit direct naar de branch '<strong class=\"branch-name\">%s</strong>'.",
|
||||
"repo.editor.create_new_branch": "Maak een <strong>nieuwe branch</strong> voor deze commit en start van een pull-aanvraag.",
|
||||
"repo.editor.create_new_branch_np": "Maak een <strong>nieuwe branch</strong> voor deze commit.",
|
||||
"repo.editor.propose_file_change": "Stel bestandswijziging voor",
|
||||
"repo.editor.new_branch_name_desc": "Nieuwe branch naam…",
|
||||
"repo.editor.new_branch_name_desc": "Nieuwe branch naam\u00e2\u20ac\u00a6",
|
||||
"repo.editor.cancel": "Annuleer",
|
||||
"repo.editor.filename_cannot_be_empty": "Bestandsnaam mag niet leeg zijn.",
|
||||
"repo.editor.file_changed_while_editing": "De bestandsinhoud is veranderd sinds je bent begonnen met bewerken. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Klik hier</a> om ze te zien, of <strong>commit de veranderingen opnieuw</strong> om ze te overschrijven.",
|
||||
@@ -825,7 +825,7 @@
|
||||
"repo.editor.push_rejected_no_message": "De wijziging is afgewezen door de server zonder bericht. Controleer de Git Hooks alsjeblieft.",
|
||||
"repo.editor.push_rejected": "De wijziging is afgewezen door de server. Controleer Controleer de Git Hooks alsjeblieft.",
|
||||
"repo.editor.push_rejected_summary": "Volledig afwijzingsbericht:",
|
||||
"repo.editor.add_subdir": "Een map toevoegen…",
|
||||
"repo.editor.add_subdir": "Een map toevoegen\u00e2\u20ac\u00a6",
|
||||
"repo.editor.no_commit_to_branch": "Kan niet rechtstreeks naar branch committen omdat:",
|
||||
"repo.editor.user_no_push_to_branch": "Gebruiker kan niet pushen naar branch",
|
||||
"repo.editor.require_signed_commit": "Branch vereist een ondertekende commit",
|
||||
@@ -857,7 +857,7 @@
|
||||
"repo.projects.create": "Project aanmaken",
|
||||
"repo.projects.title": "Titel",
|
||||
"repo.projects.new": "Nieuw project",
|
||||
"repo.projects.new_subheader": "Coördineer, track en update uw werk op één plek, dus projecten blijven transparant en op schema.",
|
||||
"repo.projects.new_subheader": "Co\u00c3\u00b6rdineer, track en update uw werk op \u00c3\u00a9\u00c3\u00a9n plek, dus projecten blijven transparant en op schema.",
|
||||
"repo.projects.deletion": "Project verwijderen",
|
||||
"repo.projects.deletion_desc": "Als een project wordt verwijdert, wordt deze van alle gerelateerde kwesties verwijderd. Doorgaan?",
|
||||
"repo.projects.deletion_success": "Het project is verwijderd.",
|
||||
@@ -971,7 +971,7 @@
|
||||
"repo.issues.num_comments": "%d opmerkingen",
|
||||
"repo.issues.commented_at": "reageerde <a href=\"#%s\">%s</a>",
|
||||
"repo.issues.delete_comment_confirm": "Weet u zeker dat u deze reactie wilt verwijderen?",
|
||||
"repo.issues.context.copy_link": "Link kopiëren",
|
||||
"repo.issues.context.copy_link": "Link kopi\u00c3\u00abren",
|
||||
"repo.issues.context.quote_reply": "Citeer antwoord",
|
||||
"repo.issues.context.reference_issue": "Verwijs in nieuw issue",
|
||||
"repo.issues.context.edit": "Bewerken",
|
||||
@@ -1071,7 +1071,7 @@
|
||||
"repo.issues.dependency.title": "Afhankelijkheden",
|
||||
"repo.issues.dependency.issue_no_dependencies": "Geen afhankelijkheden ingesteld.",
|
||||
"repo.issues.dependency.pr_no_dependencies": "Geen afhankelijkheden ingesteld.",
|
||||
"repo.issues.dependency.add": "Voeg afhankelijkheid toe…",
|
||||
"repo.issues.dependency.add": "Voeg afhankelijkheid toe\u00e2\u20ac\u00a6",
|
||||
"repo.issues.dependency.cancel": "Annuleer",
|
||||
"repo.issues.dependency.remove": "Verwijder",
|
||||
"repo.issues.dependency.remove_info": "Verwijder afhankelijkheid",
|
||||
@@ -1115,7 +1115,7 @@
|
||||
"repo.issues.reference_issue.body": "Inhoud",
|
||||
"repo.issues.content_history.deleted": "verwijderd",
|
||||
"repo.issues.content_history.edited": "bewerkt",
|
||||
"repo.issues.content_history.created": "gecreëerd",
|
||||
"repo.issues.content_history.created": "gecre\u00c3\u00aberd",
|
||||
"repo.issues.content_history.delete_from_history": "Uit geschiedenis verwijderen",
|
||||
"repo.issues.content_history.delete_from_history_confirm": "Uit geschiedenis verwijderen?",
|
||||
"repo.issues.content_history.options": "Opties",
|
||||
@@ -1251,7 +1251,7 @@
|
||||
"repo.wiki.back_to_wiki": "Terug naar wiki-pagina",
|
||||
"repo.wiki.delete_page_button": "Verwijder pagina",
|
||||
"repo.wiki.page_already_exists": "Er bestaat al een wiki-pagina met deze naam.",
|
||||
"repo.wiki.pages": "Pagina’s",
|
||||
"repo.wiki.pages": "Pagina\u00e2\u20ac\u2122s",
|
||||
"repo.wiki.last_updated": "Laatst bijgewerkt: %s",
|
||||
"repo.wiki.page_name_desc": "Voer een naam in voor deze Wiki pagina. Sommige speciale namen zijn: 'Home', '_Sidebar' en '_Footer'.",
|
||||
"repo.activity": "Activiteit",
|
||||
@@ -1411,9 +1411,9 @@
|
||||
"repo.settings.discord_icon_url": "Icoon URL",
|
||||
"repo.settings.event_desc": "Trigger op:",
|
||||
"repo.settings.event_send_everything": "Alle gebeurtenissen",
|
||||
"repo.settings.event_choose": "Aangepaste gebeurtenissen…",
|
||||
"repo.settings.event_choose": "Aangepaste gebeurtenissen\u00e2\u20ac\u00a6",
|
||||
"repo.settings.event_header_repository": "Repository gebeurtenissen",
|
||||
"repo.settings.event_create": "Creëer",
|
||||
"repo.settings.event_create": "Cre\u00c3\u00aber",
|
||||
"repo.settings.event_create_desc": "Branch, of tag aangemaakt.",
|
||||
"repo.settings.event_delete": "Verwijder",
|
||||
"repo.settings.event_delete_desc": "Branch of tag verwijderd.",
|
||||
@@ -1487,13 +1487,13 @@
|
||||
"repo.settings.require_signed_commits_desc": "Weiger pushes naar deze branch als deze niet ondertekend of niet verifieerbaar is.",
|
||||
"repo.settings.protected_branch_deletion_desc": "Branch bescherming uitschakelen zorgt ervoor dat gebruikers met schrijfrechten naar de branch kunnen pushen. Doorgaan?",
|
||||
"repo.settings.block_rejected_reviews": "Samenvoegen van afgewezen beoordelingen blokkeren",
|
||||
"repo.settings.block_rejected_reviews_desc": "Samenvoegen zal niet mogelijk zijn wanneer er wijzigingen worden aangevraagd door officiële beoordelaars, zelfs niet als er genoeg goedkeuringen zijn.",
|
||||
"repo.settings.block_on_official_review_requests": "Blokkeer de samenvoeging van officiële beoordelingsverzoeken",
|
||||
"repo.settings.block_on_official_review_requests_desc": "Samenvoegen is niet mogelijk wanneer het officiële herzieningsverzoeken heeft, ook al zijn er genoeg goedkeuringen.",
|
||||
"repo.settings.block_rejected_reviews_desc": "Samenvoegen zal niet mogelijk zijn wanneer er wijzigingen worden aangevraagd door offici\u00c3\u00able beoordelaars, zelfs niet als er genoeg goedkeuringen zijn.",
|
||||
"repo.settings.block_on_official_review_requests": "Blokkeer de samenvoeging van offici\u00c3\u00able beoordelingsverzoeken",
|
||||
"repo.settings.block_on_official_review_requests_desc": "Samenvoegen is niet mogelijk wanneer het offici\u00c3\u00able herzieningsverzoeken heeft, ook al zijn er genoeg goedkeuringen.",
|
||||
"repo.settings.block_outdated_branch": "Samenvoegen blokkeren als pull request verouderd is",
|
||||
"repo.settings.block_outdated_branch_desc": "Samenvoegen is niet mogelijk als de hoofd branch achter loop op de basis branch.",
|
||||
"repo.settings.default_branch_desc": "Selecteer een standaard repository branch voor pull requests en code commits:",
|
||||
"repo.settings.choose_branch": "Kies een branch…",
|
||||
"repo.settings.choose_branch": "Kies een branch\u00e2\u20ac\u00a6",
|
||||
"repo.settings.no_protected_branch": "Er zijn geen beschermde branches.",
|
||||
"repo.settings.edit_protected_branch": "Bewerken",
|
||||
"repo.settings.protected_branch_required_approvals_min": "Vereiste goedkeuringen kunnen niet negatief zijn.",
|
||||
@@ -1572,7 +1572,7 @@
|
||||
"repo.diff.load": "Laad Diff",
|
||||
"repo.diff.generated": "gegenereerd",
|
||||
"repo.diff.comment.placeholder": "Opmerking toevoegen",
|
||||
"repo.diff.comment.add_single_comment": "Één reactie toevoegen",
|
||||
"repo.diff.comment.add_single_comment": "\u00c3\u2030\u00c3\u00a9n reactie toevoegen",
|
||||
"repo.diff.comment.add_review_comment": "Voeg commentaar toe",
|
||||
"repo.diff.comment.start_review": "Review starten",
|
||||
"repo.diff.comment.reply": "Reageer",
|
||||
@@ -1600,7 +1600,7 @@
|
||||
"repo.release.source_code": "Broncode",
|
||||
"repo.release.tag_name": "Tagnaam",
|
||||
"repo.release.target": "Doel",
|
||||
"repo.release.tag_helper": "Kies een bestaande tag, of creëer een nieuwe tag bij publiceren.",
|
||||
"repo.release.tag_helper": "Kies een bestaande tag, of cre\u00c3\u00aber een nieuwe tag bij publiceren.",
|
||||
"repo.release.prerelease_desc": "Markeren als voorlopige versie",
|
||||
"repo.release.prerelease_helper": "Markeer deze release als ongeschikt voor productiedoeleinden.",
|
||||
"repo.release.cancel": "Annuleren",
|
||||
@@ -1652,8 +1652,8 @@
|
||||
"org.settings.visibility": "Zichtbaarheid",
|
||||
"org.settings.visibility.public": "Publiek",
|
||||
"org.settings.visibility.limited_shortname": "Beperkt",
|
||||
"org.settings.visibility.private": "Privé (alleen zichtbaar voor organisatieleden)",
|
||||
"org.settings.visibility.private_shortname": "Privé",
|
||||
"org.settings.visibility.private": "Priv\u00c3\u00a9 (alleen zichtbaar voor organisatieleden)",
|
||||
"org.settings.visibility.private_shortname": "Priv\u00c3\u00a9",
|
||||
"org.settings.update_settings": "Instellingen bijwerken",
|
||||
"org.settings.update_avatar_success": "De avatar van de organisatie is aangepast.",
|
||||
"org.settings.delete": "Verwijder organisatie",
|
||||
@@ -1807,7 +1807,7 @@
|
||||
"admin.users.allow_create_organization": "Mag organisaties aanmaken",
|
||||
"admin.users.update_profile": "Update gebruikers account",
|
||||
"admin.users.delete_account": "Verwijder gebruikers account",
|
||||
"admin.users.still_own_repo": "Deze gebruiker is nog steeds eigenaar van één of meerdere repositories. Verwijder of draag eerst deze repositories over.",
|
||||
"admin.users.still_own_repo": "Deze gebruiker is nog steeds eigenaar van \u00c3\u00a9\u00c3\u00a9n of meerdere repositories. Verwijder of draag eerst deze repositories over.",
|
||||
"admin.users.still_has_org": "Deze gebruiker is lid van een organisatie. Verwijder de gebruiker eerst uit alle organisaties.",
|
||||
"admin.users.deletion_success": "De gebruiker is verwijderd.",
|
||||
"admin.users.list_status_filter.is_active": "Actief",
|
||||
@@ -2224,24 +2224,24 @@
|
||||
"search.search": "Zoeken",
|
||||
"search.fuzzy_tooltip": "Include results that closely match the search term",
|
||||
"search.regexp": "Regexp",
|
||||
"search.repo_kind": "Search repos…",
|
||||
"search.user_kind": "Search users…",
|
||||
"search.org_kind": "Search orgs…",
|
||||
"search.team_kind": "Search teams…",
|
||||
"search.code_kind": "Search code…",
|
||||
"search.package_kind": "Search packages…",
|
||||
"search.project_kind": "Search projects…",
|
||||
"search.branch_kind": "Search branches…",
|
||||
"search.tag_kind": "Search tags…",
|
||||
"search.commit_kind": "Search commits…",
|
||||
"search.runner_kind": "Search runners…",
|
||||
"search.issue_kind": "Search issues…",
|
||||
"search.pull_kind": "Search pull requests…",
|
||||
"search.repo_kind": "Search repos\u2026",
|
||||
"search.user_kind": "Search users\u2026",
|
||||
"search.org_kind": "Search orgs\u2026",
|
||||
"search.team_kind": "Search teams\u2026",
|
||||
"search.code_kind": "Search code\u2026",
|
||||
"search.package_kind": "Search packages\u2026",
|
||||
"search.project_kind": "Search projects\u2026",
|
||||
"search.branch_kind": "Search branches\u2026",
|
||||
"search.tag_kind": "Search tags\u2026",
|
||||
"search.commit_kind": "Search commits\u2026",
|
||||
"search.runner_kind": "Search runners\u2026",
|
||||
"search.issue_kind": "Search issues\u2026",
|
||||
"search.pull_kind": "Search pull requests\u2026",
|
||||
"aria.footer.links": "Links",
|
||||
"editor.buttons.strikethrough.tooltip": "Add strikethrough text",
|
||||
"filter.string.asc": "A–Z",
|
||||
"filter.string.desc": "Z–A",
|
||||
"install.installing_desc": "Installing now, please wait…",
|
||||
"filter.string.asc": "A\u2013Z",
|
||||
"filter.string.desc": "Z\u2013A",
|
||||
"install.installing_desc": "Installing now, please wait\u2026",
|
||||
"install.db_schema": "Schema",
|
||||
"install.ssl_mode": "SSL",
|
||||
"install.reinstall_confirm_check_1": "The data encrypted by the SECRET_KEY in app.ini may be lost: users may not be able to log in with 2FA/OTP and mirrors may not function correctly. By checking this box, you confirm that the current app.ini file contains the correct SECRET_KEY.",
|
||||
@@ -2322,7 +2322,7 @@
|
||||
"repo.mirror_address_url_invalid": "The provided URL is invalid. Make sure all components of the URL are escaped correctly.",
|
||||
"repo.mirror_lfs_endpoint_desc": "Sync will attempt to use the clone URL to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else.",
|
||||
"repo.forks": "Forks",
|
||||
"repo.adopt_search": "Enter username to search for unadopted repositories… (leave blank to find all)",
|
||||
"repo.adopt_search": "Enter username to search for unadopted repositories\u2026 (leave blank to find all)",
|
||||
"repo.desc.sha256": "SHA256",
|
||||
"repo.template.webhooks": "Webhooks",
|
||||
"repo.archive.title": "This repo is archived. You can view files and clone it. You cannot open issues or pull requests or push a commit.",
|
||||
@@ -2333,7 +2333,7 @@
|
||||
"repo.migrate_items_releases": "Releases",
|
||||
"repo.migrate.github_token_desc": "You can put one or more tokens here, separated by commas, to make migrating faster by circumventing the GitHub API rate limit. WARNING: Abusing this feature may violate the service provider's policy and may lead to getting your account(s) blocked.",
|
||||
"repo.migrate.permission_denied_blocked": "You cannot import from disallowed hosts. Please ask the admin to check ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings.",
|
||||
"repo.migrate.migrating": "Migrating from <b>%s</b>…",
|
||||
"repo.migrate.migrating": "Migrating from <b>%s</b>\u2026",
|
||||
"repo.migrate.codecommit.aws_access_key_id": "AWS Access Key ID",
|
||||
"repo.migrate.codecommit.aws_secret_access_key": "AWS Secret Access Key",
|
||||
"repo.migration_status": "Migration status",
|
||||
@@ -2388,14 +2388,14 @@
|
||||
"repo.issues.review.review": "Review",
|
||||
"repo.issues.assignee.error": "Not all assignees were added, due to an unexpected error.",
|
||||
"repo.compare.title": "Comparing changes",
|
||||
"repo.compare.description": "Choose two branches or tags to see what’s changed or to start a new pull request.",
|
||||
"repo.compare.description": "Choose two branches or tags to see what\u2019s changed or to start a new pull request.",
|
||||
"repo.pulls.new.description": "Discuss and review the changes in this comparison with others.",
|
||||
"repo.pulls.new.already_existed": "A pull request between these branches already exists",
|
||||
"repo.pulls.edit.already_changed": "Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes.",
|
||||
"repo.pulls.select_commit_hold_shift_for_range": "Select commit. Hold Shift and click to select a range.",
|
||||
"repo.pulls.nothing_to_compare_have_tag": "The selected branches/tags are equal.",
|
||||
"repo.pulls.tab_commits": "Commits",
|
||||
"repo.pulls.is_checking": "Checking for merge conflicts…",
|
||||
"repo.pulls.is_checking": "Checking for merge conflicts\u2026",
|
||||
"repo.pulls.wrong_commit_id": "commit ID must be a commit ID on the target branch",
|
||||
"repo.pulls.no_merge_not_ready": "This pull request is not ready to be merged. Check review status and status checks.",
|
||||
"repo.pulls.rebase_merge_pull_request": "Rebase, then fast-forward",
|
||||
@@ -2413,7 +2413,7 @@
|
||||
"repo.pulls.status_checks_need_approvals_helper": "The workflow will only run after approval from the repository maintainer.",
|
||||
"repo.pulls.cmd_instruction_checkout_title": "Checkout",
|
||||
"repo.pulls.cmd_instruction_merge_warning": "Warning: This operation cannot merge pull request because \"autodetect manual merge\" is not enabled.",
|
||||
"repo.pulls.clear_merge_message_hint": "Clearing the merge message will only remove the commit message content and keep generated git trailers such as \"Co-Authored-By…\".",
|
||||
"repo.pulls.clear_merge_message_hint": "Clearing the merge message will only remove the commit message content and keep generated git trailers such as \"Co-Authored-By\u2026\".",
|
||||
"repo.signing.wont_sign.error": "There was an error while checking if the commit could be signed.",
|
||||
"repo.signing.wont_sign.twofa": "You must have two-factor authentication enabled to have commits signed.",
|
||||
"repo.wiki": "Wiki",
|
||||
@@ -2474,8 +2474,8 @@
|
||||
"repo.settings.visibility.private.bullet_two": "May remove the relationship between it and <strong>forks</strong>, <strong>watchers</strong>, and <strong>stars</strong>.",
|
||||
"repo.settings.unarchive.text": "Unarchiving the repo will restore its ability to receive commits and pushes, as well as new issues and pull requests.",
|
||||
"repo.settings.lfs": "LFS",
|
||||
"repo.settings.lfs_lock_path": "Filepath to lock…",
|
||||
"repo.settings.lfs_pointers.found": "Found %d blob pointer(s) — %d associated, %d unassociated (%d missing from store)",
|
||||
"repo.settings.lfs_lock_path": "Filepath to lock\u2026",
|
||||
"repo.settings.lfs_pointers.found": "Found %d blob pointer(s) \u2014 %d associated, %d unassociated (%d missing from store)",
|
||||
"repo.settings.lfs_pointers.sha": "Blob SHA",
|
||||
"repo.settings.lfs_pointers.oid": "OID",
|
||||
"repo.settings.pages": "Pagina's",
|
||||
@@ -2535,7 +2535,7 @@
|
||||
"repo.branch.commits_divergence_from": "Commit divergence: %[1]d behind and %[2]d ahead of %[3]s",
|
||||
"repo.branch.commits_no_divergence": "The same as branch %[1]s",
|
||||
"repo.find_file.follow_symlink": "Follow this symlink to where it is pointing at",
|
||||
"graphs.component_loading": "Loading %s…",
|
||||
"graphs.component_loading": "Loading %s\u2026",
|
||||
"org.teams": "Teams",
|
||||
"org.pinned_repos": "Featured Projects",
|
||||
"org.public_members": "Public Members",
|
||||
@@ -2544,7 +2544,7 @@
|
||||
"org.settings.email": "Contact Email Address",
|
||||
"org.settings.change_visibility": "Change Visibility",
|
||||
"org.settings.change_visibility_notices_1": "If the organization is converted to private, the repository stars will be removed and cannot be restored.",
|
||||
"org.settings.change_visibility_notices_2": "Non-members will lose access to the organization’s repositories if visibility is changed to private.",
|
||||
"org.settings.change_visibility_notices_2": "Non-members will lose access to the organization\u2019s repositories if visibility is changed to private.",
|
||||
"org.settings.change_visibility_success": "The visibility of organization %s has been successfully changed.",
|
||||
"org.settings.visibility_desc": "Change who can view the organization and its repositories.",
|
||||
"org.settings.rename": "Rename Organization",
|
||||
@@ -2660,12 +2660,12 @@
|
||||
"packages.settings.global_access.disabled": "Globale toegang tot pakket is uitgeschakeld.",
|
||||
"packages.settings.global_access.error": "Kon globale toegangsinstelling niet bijwerken.",
|
||||
"packages.visibility": "Zichtbaarheid",
|
||||
"packages.settings.visibility.private.text": "Dit pakket is momenteel privé. Maak het openbaar om iedereen toegang te geven.",
|
||||
"packages.settings.visibility.private.button": "Privé maken",
|
||||
"packages.settings.visibility.private.bullet_title": "U staat op het punt dit pakket privé te maken.",
|
||||
"packages.settings.visibility.private.text": "Dit pakket is momenteel priv\u00e9. Maak het openbaar om iedereen toegang te geven.",
|
||||
"packages.settings.visibility.private.button": "Priv\u00e9 maken",
|
||||
"packages.settings.visibility.private.bullet_title": "U staat op het punt dit pakket priv\u00e9 te maken.",
|
||||
"packages.settings.visibility.private.bullet_one": "Alleen gebruikers met de juiste rechten kunnen dit pakket openen.",
|
||||
"packages.settings.visibility.private.success": "Pakket is nu privé.",
|
||||
"packages.settings.visibility.public.text": "Dit pakket is momenteel openbaar. Maak het privé om de toegang te beperken.",
|
||||
"packages.settings.visibility.private.success": "Pakket is nu priv\u00e9.",
|
||||
"packages.settings.visibility.public.text": "Dit pakket is momenteel openbaar. Maak het priv\u00e9 om de toegang te beperken.",
|
||||
"packages.settings.visibility.public.button": "Openbaar maken",
|
||||
"packages.settings.visibility.public.bullet_title": "U staat op het punt dit pakket openbaar te maken.",
|
||||
"packages.settings.visibility.public.bullet_one": "Iedereen kan dit pakket openen en downloaden.",
|
||||
@@ -2714,7 +2714,7 @@
|
||||
"actions.general.collaborative_owner_not_exist": "Collaboratieve eigenaar bestaat niet",
|
||||
"actions.general.remove_collaborative_owner": "Collaboratieve eigenaar verwijderen",
|
||||
"actions.general.remove_collaborative_owner_desc": "De eigenaar verliest toegang tot runnerbeheer",
|
||||
"git.filemode.changed_filemode": "%[1]s → %[2]s",
|
||||
"git.filemode.changed_filemode": "%[1]s \u2192 %[2]s",
|
||||
"org.pinned_repos_empty_title": "Showcase your best work",
|
||||
"org.pinned_repos_empty_desc": "Pin up to 6 repositories to highlight your organization's most important projects.",
|
||||
"org.settings.pinned.manage": "Manage Pins",
|
||||
@@ -2781,7 +2781,7 @@
|
||||
"admin.config.pinned_org_format_condensed": "Compact",
|
||||
"admin.config.pinned_org_format_regular": "Normaal",
|
||||
"admin.config.pinned_org_display_format_help": "Weergaveformaat voor vastgepinde organisaties",
|
||||
"admin.config.logo_upload_success": "Logo geüpload",
|
||||
"admin.config.logo_upload_success": "Logo ge\u00fcpload",
|
||||
"admin.config.logo_url_success": "Logo-URL ingesteld",
|
||||
"admin.config.logo_reset_success": "Logo gereset",
|
||||
"admin.config.logo_invalid_type": "Ongeldig logo-bestandstype",
|
||||
@@ -2792,7 +2792,7 @@
|
||||
"admin.config.reset_icon": "Pictogram resetten",
|
||||
"admin.config.current_icon": "Huidig pictogram",
|
||||
"admin.config.icon_url": "Pictogram-URL",
|
||||
"admin.config.icon_upload_success": "Pictogram geüpload",
|
||||
"admin.config.icon_upload_success": "Pictogram ge\u00fcpload",
|
||||
"admin.config.icon_url_success": "Pictogram-URL ingesteld",
|
||||
"admin.config.icon_reset_success": "Pictogram gereset",
|
||||
"admin.config.icon_invalid_type": "Ongeldig bestandstype. Toegestaan: SVG, PNG, ICO",
|
||||
@@ -2884,6 +2884,12 @@
|
||||
"repo.settings.pages.nav_show_repository": "Repository-link tonen (Broncode bekijken-knop)",
|
||||
"repo.settings.pages.nav_show_releases": "Releases-link tonen",
|
||||
"repo.settings.pages.nav_show_issues": "Issues-link tonen",
|
||||
"repo.settings.pages.section_labels": "Sectielabels",
|
||||
"repo.settings.pages.section_labels_desc": "Pas de koppen aan die op uw landingspagina worden weergegeven voor elke sectie",
|
||||
"repo.settings.pages.label_value_props": "Kop waardeproposities",
|
||||
"repo.settings.pages.label_value_props_help": "Kop voor de sectie waardeproposities (bijv., \"Waarom voor ons kiezen\")",
|
||||
"repo.settings.pages.label_features": "Kop functies",
|
||||
"repo.settings.pages.label_features_help": "Kop voor de sectie functies (bijv., \"Mogelijkheden\")",
|
||||
"repo.settings.pages.blog_section": "Blogsectie",
|
||||
"repo.settings.pages.blog_enabled_desc": "Toon recente blogberichten op de landingspagina",
|
||||
"repo.settings.pages.blog_headline": "Blogtitel",
|
||||
@@ -3074,7 +3080,7 @@
|
||||
"repo.settings.gallery_upload": "Afbeeldingen uploaden",
|
||||
"repo.settings.gallery_current": "Huidige afbeeldingen",
|
||||
"repo.settings.gallery_empty": "Nog geen galerij-afbeeldingen. Upload er enkele om uw project te tonen!",
|
||||
"repo.settings.gallery_uploaded": "Afbeelding succesvol geüpload.",
|
||||
"repo.settings.gallery_uploaded": "Afbeelding succesvol ge\u00fcpload.",
|
||||
"repo.settings.gallery_deleted": "Afbeelding succesvol verwijderd.",
|
||||
"repo.settings.gallery_caption_saved": "Bijschrift succesvol opgeslagen.",
|
||||
"repo.settings.gallery_caption_placeholder": "Voeg een bijschrift toe...",
|
||||
@@ -3131,12 +3137,12 @@
|
||||
"vault.back_to_secret": "Terug naar Geheim",
|
||||
"vault.back_to_versions": "Terug naar Versies",
|
||||
"vault.show_value": "Waarde tonen",
|
||||
"vault.copy_value": "Kopiëren naar klembord",
|
||||
"vault.copy_value": "Kopi\u00ebren naar klembord",
|
||||
"vault.view_hidden": "Verborgen bekijken",
|
||||
"vault.view_raw": "Ruwe waarde bekijken",
|
||||
"vault.hidden": "Verborgen",
|
||||
"vault.raw": "Ruw",
|
||||
"vault.copy": "Kopiëren",
|
||||
"vault.copy": "Kopi\u00ebren",
|
||||
"vault.copied": "Gekopieerd!",
|
||||
"vault.multiline_hint": "Ondersteunt meerregelige inhoud (env-bestanden, certificaten, enz.)",
|
||||
"vault.rollback": "Terugdraaien",
|
||||
@@ -3254,5 +3260,68 @@
|
||||
"repo.view_file": "Bestand bekijken",
|
||||
"actions.runners.waiting_jobs": "Wachtende taken",
|
||||
"actions.runners.back_to_runners": "Terug naar runners",
|
||||
"actions.runners.no_waiting_jobs": "Geen taken wachten op dit label"
|
||||
}
|
||||
"actions.runners.no_waiting_jobs": "Geen taken wachten op dit label",
|
||||
"repo.settings.pages.cross_promote_section": "Kruispromotie-sectie",
|
||||
"repo.settings.pages.cross_promote_enabled_desc": "Toon cross-gepromote repositories op de landingspagina",
|
||||
"repo.settings.pages.cross_promote_headline": "Sectiekop",
|
||||
"repo.settings.pages.cross_promote_subheadline": "Sectie-ondertitel",
|
||||
"repo.settings.pages.cross_promote_help": "Configureer de repositories in Instellingen > Kruispromotie.",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files": "Verborgen bestanden en mappen uitsluiten",
|
||||
"repo.settings.mirror_settings.push_mirror.exclude_hidden_files_desc": "Bestanden/mappen die beginnen met \".\" of als verborgen gemarkeerd zijn niet naar de remote mirror pushen",
|
||||
"repo.settings.pages.value_props_headline": "Sectiekop",
|
||||
"repo.settings.pages.value_props_headline_help": "Grote kop voor de sectie waardeproposities. Het kleine label erboven wordt ingesteld in Sectielabels.",
|
||||
"repo.settings.pages.value_props_subheadline": "Sectie-ondertitel",
|
||||
"repo.settings.pages.features_headline": "Sectiekop",
|
||||
"repo.settings.pages.features_headline_help": "Grote kop voor de sectie functies. Het kleine label erboven wordt ingesteld in Sectielabels.",
|
||||
"repo.settings.pages.features_subheadline": "Sectie-ondertitel",
|
||||
"repo.settings.license_acl_fields": "Auditable Commercial License instellingen",
|
||||
"repo.settings.license_acl_fields_desc": "Deze velden zijn vereist om de Auditable Commercial License (ACL) v1.0 voor uw project aan te passen.",
|
||||
"repo.settings.license_acl_jurisdiction": "Rechtsgebied",
|
||||
"repo.settings.license_acl_jurisdiction_help": "Toepasselijk recht voor Sectie 8.1 (bijv., \"the State of Delaware, United States\").",
|
||||
"repo.settings.license_acl_venue": "Locatie",
|
||||
"repo.settings.license_acl_venue_help": "Rechtbank locatie voor geschillen (bijv., \"Wilmington, Delaware\").",
|
||||
"repo.settings.license_acl_contact": "Contactadres (optioneel)",
|
||||
"repo.settings.license_acl_contact_help": "Waar juridische kennisgevingen heen moeten. Voegt een Notices-sectie toe indien opgegeven.",
|
||||
"repo.settings.license_acl_license_url": "Licentie URL (optioneel)",
|
||||
"repo.settings.license_acl_tier_url": "Tier schema URL (optioneel)",
|
||||
"repo.settings.license_acl_required": "Rechtsgebied en Locatie zijn vereist voor de Auditable Commercial License.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_tooltip": "Mirror resetten naar specifieke commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_title": "Push-mirror resetten naar commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_title": "Destructieve actie",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_warning_body": "Dit zal de gekozen commit afdwingen op de remote en de branchgeschiedenis daar herschrijven. Auto-sync wordt gepauzeerd.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch": "Branch op remote",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_branch_help": "De branch op de remote mirror om te resetten.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit": "Commit SHA",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_commit_help": "Volledige SHA van de commit in deze repository om naar de remote te pushen.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_confirm": "Force-push en pauzeer sync",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_required": "Branch en commit SHA zijn vereist.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_failed": "Mirror reset mislukt: %s",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_success": "Mirror reset voltooid. Auto-sync is gepauzeerd.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button": "Reset alle mirrors naar een commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_button_help": "Force-push dezelfde commit naar elke push-mirror tegelijk en pauzeer alle auto-sync.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_title": "Reset alle push-mirrors naar commit",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_title": "Destructieve actie \u2014 be\u00efnvloedt alle mirrors",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_warning_body": "Dit force-pusht de gekozen commit naar de genoemde branch op elke geconfigureerde push-mirror.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_confirm": "Force-push alle mirrors en pauzeer sync",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_success": "Reset voltooid. %d mirror(s) bijgewerkt.",
|
||||
"repo.settings.mirror_settings.push_mirror.reset_all_partial": "Gedeeltelijke reset: %d geslaagd, %d mislukt. Fouten: %s",
|
||||
"repo.settings.repo_reset.title": "Repository-geschiedenis resetten",
|
||||
"repo.settings.repo_reset.desc": "Verplaatst de HEAD van een branch naar een specifieke commit en force-pusht naar alle mirrors.",
|
||||
"repo.settings.repo_reset.button": "Reset naar commit",
|
||||
"repo.settings.repo_reset.warning_title": "Dit herschrijft de repository-geschiedenis",
|
||||
"repo.settings.repo_reset.warning_body": "Deze actie zal:",
|
||||
"repo.settings.repo_reset.warning_branch": "De HEAD van de branch op deze server verplaatsen naar de gekozen commit.",
|
||||
"repo.settings.repo_reset.warning_collaborators": "Iedereen die de repository heeft gekloond of getrokken moet opnieuw klonen.",
|
||||
"repo.settings.repo_reset.warning_mirrors": "Alle push-mirrors worden geforceerd bijgewerkt en hun auto-sync wordt gepauzeerd.",
|
||||
"repo.settings.repo_reset.branch": "Branch om te resetten",
|
||||
"repo.settings.repo_reset.commit": "Commit SHA",
|
||||
"repo.settings.repo_reset.confirm_name": "Typ ter bevestiging de repository-naam: %s",
|
||||
"repo.settings.repo_reset.confirm": "Repository resetten",
|
||||
"repo.settings.repo_reset.required": "Branch en commit SHA zijn vereist.",
|
||||
"repo.settings.repo_reset.confirm_mismatch": "Bevestiging van repository-naam komt niet overeen.",
|
||||
"repo.settings.repo_reset.commit_not_found": "Commit %s niet gevonden.",
|
||||
"repo.settings.repo_reset.branch_not_found": "Branch %s bestaat niet.",
|
||||
"repo.settings.repo_reset.failed": "Reset mislukt: %s",
|
||||
"repo.settings.repo_reset.success": "Reset voltooid.",
|
||||
"repo.settings.repo_reset.partial": "Repository lokaal gereset, maar sommige mirrors mislukten: %s"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -248,6 +248,26 @@ func Routes() *web.Router {
|
||||
}, reqToken())
|
||||
}, repoAssignment())
|
||||
|
||||
// Organization v2 API - org overview, repos, profile, pinned repos
|
||||
m.Group("/user/orgs", func() {
|
||||
m.Get("", ListUserOrgsV2)
|
||||
}, reqToken())
|
||||
|
||||
m.Group("/orgs/{org}", func() {
|
||||
// Public read endpoints
|
||||
m.Get("/overview", GetOrgOverviewV2)
|
||||
m.Get("/repos", ListOrgReposV2)
|
||||
m.Get("/profile/readme", GetOrgProfileReadmeV2)
|
||||
|
||||
// Write endpoints require authentication + org ownership (checked in handler)
|
||||
m.Group("", func() {
|
||||
m.Patch("", web.Bind(UpdateOrgV2Option{}), UpdateOrgV2)
|
||||
m.Put("/profile/readme", web.Bind(UpdateOrgProfileReadmeOption{}), UpdateOrgProfileReadmeV2)
|
||||
m.Post("/pinned", web.Bind(PinOrgRepoV2Option{}), PinOrgRepoV2)
|
||||
m.Delete("/pinned/{repo}", UnpinOrgRepoV2)
|
||||
}, reqToken())
|
||||
})
|
||||
|
||||
// AI settings API - org-scoped AI settings
|
||||
m.Group("/orgs/{org}/ai", func() {
|
||||
m.Get("/settings", GetOrgAISettingsV2)
|
||||
|
||||
@@ -685,9 +685,11 @@ func handleInitialize(ctx *context_service.APIContext, req *MCPRequest) {
|
||||
}
|
||||
|
||||
func handleToolsList(ctx *context_service.APIContext, req *MCPRequest) {
|
||||
allTools := make([]MCPTool, 0, len(mcpTools)+len(mcpAITools))
|
||||
allTools := make([]MCPTool, 0, len(mcpTools)+len(mcpAITools)+len(mcpPagesTools)+len(mcpOrgTools))
|
||||
allTools = append(allTools, mcpTools...)
|
||||
allTools = append(allTools, mcpAITools...)
|
||||
allTools = append(allTools, mcpPagesTools...)
|
||||
allTools = append(allTools, mcpOrgTools...)
|
||||
result := MCPToolsListResult{Tools: allTools}
|
||||
sendMCPResult(ctx, req.ID, result)
|
||||
}
|
||||
@@ -759,6 +761,52 @@ func handleToolsCall(ctx *context_service.APIContext, req *MCPRequest) {
|
||||
result, err = toolListIssues(ctx, params.Arguments)
|
||||
case "get_issue":
|
||||
result, err = toolGetIssue(ctx, params.Arguments)
|
||||
// Landing Pages tools
|
||||
case "get_landing_config":
|
||||
result, err = toolGetLandingConfig(ctx, params.Arguments)
|
||||
case "list_landing_templates":
|
||||
result, err = toolListLandingTemplates(ctx, params.Arguments)
|
||||
case "enable_landing_page":
|
||||
result, err = toolEnableLandingPage(ctx, params.Arguments)
|
||||
case "update_landing_brand":
|
||||
result, err = toolUpdateLandingBrand(ctx, params.Arguments)
|
||||
case "update_landing_hero":
|
||||
result, err = toolUpdateLandingHero(ctx, params.Arguments)
|
||||
case "update_landing_pricing":
|
||||
result, err = toolUpdateLandingPricing(ctx, params.Arguments)
|
||||
case "update_landing_comparison":
|
||||
result, err = toolUpdateLandingComparison(ctx, params.Arguments)
|
||||
case "update_landing_features":
|
||||
result, err = toolUpdateLandingFeatures(ctx, params.Arguments)
|
||||
case "update_landing_social_proof":
|
||||
result, err = toolUpdateLandingSocialProof(ctx, params.Arguments)
|
||||
case "update_landing_seo":
|
||||
result, err = toolUpdateLandingSEO(ctx, params.Arguments)
|
||||
case "update_landing_theme":
|
||||
result, err = toolUpdateLandingTheme(ctx, params.Arguments)
|
||||
case "update_landing_stats":
|
||||
result, err = toolUpdateLandingStats(ctx, params.Arguments)
|
||||
case "update_landing_value_props":
|
||||
result, err = toolUpdateLandingValueProps(ctx, params.Arguments)
|
||||
case "update_landing_cta":
|
||||
result, err = toolUpdateLandingCTA(ctx, params.Arguments)
|
||||
// Organization tools
|
||||
case "list_orgs":
|
||||
result, err = toolListOrgs(ctx, params.Arguments)
|
||||
case "get_org_overview":
|
||||
result, err = toolGetOrgOverview(ctx, params.Arguments)
|
||||
case "update_org":
|
||||
result, err = toolUpdateOrg(ctx, params.Arguments)
|
||||
case "list_org_repos":
|
||||
result, err = toolListOrgRepos(ctx, params.Arguments)
|
||||
case "get_org_profile_readme":
|
||||
result, err = toolGetOrgProfileReadme(ctx, params.Arguments)
|
||||
case "update_org_profile_readme":
|
||||
result, err = toolUpdateOrgProfileReadme(ctx, params.Arguments)
|
||||
case "pin_org_repo":
|
||||
result, err = toolPinOrgRepo(ctx, params.Arguments)
|
||||
case "unpin_org_repo":
|
||||
result, err = toolUnpinOrgRepo(ctx, params.Arguments)
|
||||
default:
|
||||
sendMCPError(ctx, req.ID, -32602, "Unknown tool", params.Name)
|
||||
return
|
||||
|
||||
720
routers/api/v2/mcp_org.go
Normal file
720
routers/api/v2/mcp_org.go
Normal file
@@ -0,0 +1,720 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/db"
|
||||
"code.gitcaddy.com/server/v3/models/organization"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/optional"
|
||||
context_service "code.gitcaddy.com/server/v3/services/context"
|
||||
org_service "code.gitcaddy.com/server/v3/services/org"
|
||||
user_service "code.gitcaddy.com/server/v3/services/user"
|
||||
)
|
||||
|
||||
// mcpOrgTools defines MCP tools for organization management.
|
||||
var mcpOrgTools = []MCPTool{
|
||||
{
|
||||
Name: "list_orgs",
|
||||
Description: "List all organizations the authenticated user is a member of. Returns name, full name, description, avatar URL, website, location, group header, and visibility for each org.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "get_org_overview",
|
||||
Description: "Get a comprehensive overview of an organization including pinned repos, pinned groups, public members, stats (repos/members/teams/stars), profile README content, and recent repository activity.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
},
|
||||
"required": []string{"org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_org",
|
||||
Description: "Update an organization's basic information. Only provided fields are changed. Requires org owner or site admin.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
"full_name": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Display name of the organization",
|
||||
},
|
||||
"email": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Contact email address",
|
||||
},
|
||||
"description": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization description",
|
||||
},
|
||||
"website": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Website URL",
|
||||
},
|
||||
"location": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Physical location",
|
||||
},
|
||||
"group_header": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Group header for organizing this org on the explore page",
|
||||
},
|
||||
},
|
||||
"required": []string{"org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "list_org_repos",
|
||||
Description: "List repositories for an organization. Supports grouping by group_header to see how repos are organized. Respects the caller's access permissions.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
"group_by": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Group results by field. Use 'group_header' to see repos grouped by their assigned group.",
|
||||
"enum": []string{"group_header"},
|
||||
},
|
||||
"q": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Search keyword to filter repos by name",
|
||||
},
|
||||
"sort": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Sort order",
|
||||
"enum": []string{"newest", "oldest", "alphabetically", "reversealphabetically", "stars", "forks", "recentupdate"},
|
||||
},
|
||||
"limit": map[string]any{
|
||||
"type": "number",
|
||||
"description": "Number of repos to return (max 100, default 50)",
|
||||
},
|
||||
"page": map[string]any{
|
||||
"type": "number",
|
||||
"description": "Page number (1-based)",
|
||||
},
|
||||
},
|
||||
"required": []string{"org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "get_org_profile_readme",
|
||||
Description: "Get the raw markdown content of an organization's profile README from its .profile repository.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
},
|
||||
"required": []string{"org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_org_profile_readme",
|
||||
Description: "Update (or create) the organization's profile README. Creates the .profile repository if it doesn't exist. Requires org owner or site admin.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
"content": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Markdown content for the README",
|
||||
},
|
||||
"commit_message": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Git commit message (default: 'Update organization profile README')",
|
||||
},
|
||||
},
|
||||
"required": []string{"org", "content"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "pin_org_repo",
|
||||
Description: "Pin a repository to the organization's overview page. Optionally assign it to a pinned group. Requires org owner or site admin.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
"repo": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Repository name to pin",
|
||||
},
|
||||
"group_id": map[string]any{
|
||||
"type": "number",
|
||||
"description": "ID of the pinned group to add the repo to (0 or omit for ungrouped)",
|
||||
},
|
||||
"display_order": map[string]any{
|
||||
"type": "number",
|
||||
"description": "Display order within the group (lower = first)",
|
||||
},
|
||||
},
|
||||
"required": []string{"org", "repo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unpin_org_repo",
|
||||
Description: "Unpin a repository from the organization's overview page. Requires org owner or site admin.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"org": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Organization name",
|
||||
},
|
||||
"repo": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Repository name to unpin",
|
||||
},
|
||||
},
|
||||
"required": []string{"org", "repo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// --- Tool Handlers ---
|
||||
|
||||
func toolListOrgs(ctx *context_service.APIContext, _ map[string]any) (any, error) {
|
||||
if ctx.Doer == nil {
|
||||
return nil, errors.New("authentication required")
|
||||
}
|
||||
|
||||
opts := organization.FindOrgOptions{
|
||||
ListOptions: db.ListOptions{PageSize: 50},
|
||||
UserID: ctx.Doer.ID,
|
||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.Doer),
|
||||
}
|
||||
orgs, _, err := db.FindAndCount[organization.Organization](ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]map[string]any, 0, len(orgs))
|
||||
for _, org := range orgs {
|
||||
entry := map[string]any{
|
||||
"name": org.Name,
|
||||
"full_name": org.FullName,
|
||||
"description": org.Description,
|
||||
"avatar_url": org.AsUser().AvatarLink(ctx),
|
||||
"website": org.Website,
|
||||
"location": org.Location,
|
||||
"group_header": org.GroupHeader,
|
||||
"visibility": org.Visibility.String(),
|
||||
}
|
||||
result = append(result, entry)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"organizations": result,
|
||||
"count": len(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolGetOrgOverview(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
if orgName == "" {
|
||||
return nil, errors.New("org is required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
// Stats
|
||||
stats, err := org_service.GetOrgOverviewStats(ctx, org.ID, ctx.Doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pinned repos
|
||||
pinnedRepos, err := org_service.GetOrgPinnedReposWithDetails(ctx, org.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiPinned := make([]map[string]any, 0, len(pinnedRepos))
|
||||
for _, p := range pinnedRepos {
|
||||
if p.Repo == nil {
|
||||
continue
|
||||
}
|
||||
entry := map[string]any{
|
||||
"id": p.ID,
|
||||
"display_order": p.DisplayOrder,
|
||||
}
|
||||
if repo, ok := p.Repo.(*repo_model.Repository); ok {
|
||||
entry["repo_name"] = repo.Name
|
||||
entry["repo_full_name"] = repo.FullName()
|
||||
entry["description"] = repo.Description
|
||||
entry["is_private"] = repo.IsPrivate
|
||||
}
|
||||
if p.Group != nil {
|
||||
entry["group_id"] = p.GroupID
|
||||
entry["group_name"] = p.Group.Name
|
||||
}
|
||||
apiPinned = append(apiPinned, entry)
|
||||
}
|
||||
|
||||
// Pinned groups
|
||||
pinnedGroups, err := organization.GetOrgPinnedGroups(ctx, org.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiGroups := make([]map[string]any, 0, len(pinnedGroups))
|
||||
for _, g := range pinnedGroups {
|
||||
apiGroups = append(apiGroups, map[string]any{
|
||||
"id": g.ID,
|
||||
"name": g.Name,
|
||||
"display_order": g.DisplayOrder,
|
||||
"collapsed": g.Collapsed,
|
||||
})
|
||||
}
|
||||
|
||||
// Profile readme
|
||||
readme, _ := org_service.GetOrgProfileReadme(ctx, org.ID)
|
||||
|
||||
// Recent activity
|
||||
recentActivity, _ := org_service.GetOrgRecentActivity(ctx, org.ID, ctx.Doer, 10)
|
||||
apiRecent := make([]map[string]any, 0, len(recentActivity))
|
||||
for _, a := range recentActivity {
|
||||
apiRecent = append(apiRecent, map[string]any{
|
||||
"repo_name": a.RepoName,
|
||||
"repo_full_name": a.RepoFullName,
|
||||
"commit_message": a.CommitMessage,
|
||||
"commit_time": a.CommitTime,
|
||||
"is_private": a.IsPrivate,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"organization": map[string]any{
|
||||
"name": org.Name,
|
||||
"full_name": org.FullName,
|
||||
"description": org.Description,
|
||||
"website": org.Website,
|
||||
"location": org.Location,
|
||||
"group_header": org.GroupHeader,
|
||||
"visibility": org.Visibility.String(),
|
||||
},
|
||||
"stats": map[string]any{
|
||||
"total_repos": stats.TotalRepos,
|
||||
"total_members": stats.TotalMembers,
|
||||
"total_teams": stats.TotalTeams,
|
||||
"total_stars": stats.TotalStars,
|
||||
},
|
||||
"pinned_repos": apiPinned,
|
||||
"pinned_groups": apiGroups,
|
||||
"profile_readme": readme,
|
||||
"recent_activity": apiRecent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolUpdateOrg(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
if orgName == "" {
|
||||
return nil, errors.New("org is required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
// Permission check
|
||||
if ctx.Doer == nil {
|
||||
return nil, errors.New("authentication required")
|
||||
}
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isOwner, err := org.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isOwner {
|
||||
return nil, errors.New("organization admin access required")
|
||||
}
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{}
|
||||
updated := []string{}
|
||||
|
||||
if v, ok := args["full_name"].(string); ok {
|
||||
opts.FullName = optional.Some(v)
|
||||
updated = append(updated, "full_name")
|
||||
}
|
||||
if v, ok := args["description"].(string); ok {
|
||||
opts.Description = optional.Some(v)
|
||||
updated = append(updated, "description")
|
||||
}
|
||||
if v, ok := args["website"].(string); ok {
|
||||
opts.Website = optional.Some(v)
|
||||
updated = append(updated, "website")
|
||||
}
|
||||
if v, ok := args["location"].(string); ok {
|
||||
opts.Location = optional.Some(v)
|
||||
updated = append(updated, "location")
|
||||
}
|
||||
if v, ok := args["group_header"].(string); ok {
|
||||
opts.GroupHeader = optional.Some(v)
|
||||
updated = append(updated, "group_header")
|
||||
}
|
||||
|
||||
if v, ok := args["email"].(string); ok && v != "" {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), v); err != nil {
|
||||
return nil, fmt.Errorf("failed to update email: %v", err)
|
||||
}
|
||||
updated = append(updated, "email")
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"status": "updated",
|
||||
"org": orgName,
|
||||
"fields_updated": updated,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolListOrgRepos(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
if orgName == "" {
|
||||
return nil, errors.New("org is required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
limit := 50
|
||||
if v, ok := args["limit"].(float64); ok && v > 0 {
|
||||
limit = min(int(v), 100)
|
||||
}
|
||||
page := 1
|
||||
if v, ok := args["page"].(float64); ok && v > 0 {
|
||||
page = int(v)
|
||||
}
|
||||
|
||||
groupBy, _ := args["group_by"].(string)
|
||||
keyword, _ := args["q"].(string)
|
||||
sortOrder, _ := args["sort"].(string)
|
||||
|
||||
var orderBy db.SearchOrderBy
|
||||
switch sortOrder {
|
||||
case "newest":
|
||||
orderBy = db.SearchOrderByNewest
|
||||
case "oldest":
|
||||
orderBy = db.SearchOrderByOldest
|
||||
case "reversealphabetically":
|
||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
||||
case "stars":
|
||||
orderBy = db.SearchOrderByStarsReverse
|
||||
case "forks":
|
||||
orderBy = db.SearchOrderByForksReverse
|
||||
case "recentupdate":
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
default:
|
||||
orderBy = db.SearchOrderByAlphabetically
|
||||
}
|
||||
|
||||
if groupBy == "group_header" {
|
||||
orderBy = db.SearchOrderBy("CASE WHEN group_header = '' OR group_header IS NULL THEN 1 ELSE 0 END, group_header ASC, " + string(orderBy))
|
||||
}
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{PageSize: limit, Page: page},
|
||||
Keyword: keyword,
|
||||
OwnerID: org.ID,
|
||||
OrderBy: orderBy,
|
||||
Private: ctx.Doer != nil,
|
||||
Actor: ctx.Doer,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if groupBy == "group_header" {
|
||||
// Grouped response
|
||||
type repoEntry struct {
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
IsFork bool `json:"is_fork"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
Stars int `json:"stars"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
grouped := make(map[string][]repoEntry)
|
||||
var headers []string
|
||||
headerSeen := make(map[string]bool)
|
||||
|
||||
for _, repo := range repos {
|
||||
header := repo.GroupHeader
|
||||
if !headerSeen[header] {
|
||||
headerSeen[header] = true
|
||||
headers = append(headers, header)
|
||||
}
|
||||
grouped[header] = append(grouped[header], repoEntry{
|
||||
Name: repo.Name,
|
||||
FullName: repo.FullName(),
|
||||
Description: repo.Description,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
IsFork: repo.IsFork,
|
||||
IsArchived: repo.IsArchived,
|
||||
Stars: repo.NumStars,
|
||||
Language: repoLanguage(repo),
|
||||
})
|
||||
}
|
||||
|
||||
groups := make([]map[string]any, 0, len(headers))
|
||||
for _, h := range headers {
|
||||
displayName := h
|
||||
if displayName == "" {
|
||||
displayName = "(ungrouped)"
|
||||
}
|
||||
groups = append(groups, map[string]any{
|
||||
"group_header": h,
|
||||
"display_name": displayName,
|
||||
"repos": grouped[h],
|
||||
"count": len(grouped[h]),
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"groups": groups,
|
||||
"total": count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Flat response
|
||||
repoList := make([]map[string]any, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
repoList = append(repoList, map[string]any{
|
||||
"name": repo.Name,
|
||||
"full_name": repo.FullName(),
|
||||
"description": repo.Description,
|
||||
"is_private": repo.IsPrivate,
|
||||
"is_fork": repo.IsFork,
|
||||
"is_archived": repo.IsArchived,
|
||||
"stars": repo.NumStars,
|
||||
"language": repoLanguage(repo),
|
||||
"group_header": repo.GroupHeader,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"repos": repoList,
|
||||
"total": count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolGetOrgProfileReadme(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
if orgName == "" {
|
||||
return nil, errors.New("org is required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
readme, err := org_service.GetOrgProfileReadme(ctx, org.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"org": orgName,
|
||||
"content": readme,
|
||||
"has_profile": readme != "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolUpdateOrgProfileReadme(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
if orgName == "" {
|
||||
return nil, errors.New("org is required")
|
||||
}
|
||||
content, _ := args["content"].(string)
|
||||
if content == "" {
|
||||
return nil, errors.New("content is required")
|
||||
}
|
||||
commitMessage, _ := args["commit_message"].(string)
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
// Permission check
|
||||
if ctx.Doer == nil {
|
||||
return nil, errors.New("authentication required")
|
||||
}
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isOwner, err := org.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isOwner {
|
||||
return nil, errors.New("organization admin access required")
|
||||
}
|
||||
}
|
||||
|
||||
if err := org_service.UpdateOrgProfileReadme(ctx, ctx.Doer, org.ID, content, commitMessage); err != nil {
|
||||
return nil, fmt.Errorf("failed to update profile readme: %v", err)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"status": "updated",
|
||||
"org": orgName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolPinOrgRepo(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
repoName, _ := args["repo"].(string)
|
||||
if orgName == "" || repoName == "" {
|
||||
return nil, errors.New("org and repo are required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
// Permission check
|
||||
if ctx.Doer == nil {
|
||||
return nil, errors.New("authentication required")
|
||||
}
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isOwner, err := org.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isOwner {
|
||||
return nil, errors.New("organization admin access required")
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, org.ID, repoName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repository not found: %s/%s", orgName, repoName)
|
||||
}
|
||||
|
||||
isPinned, err := organization.IsRepoPinned(ctx, org.ID, repo.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isPinned {
|
||||
return map[string]any{
|
||||
"status": "already_pinned",
|
||||
"org": orgName,
|
||||
"repo": repoName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var groupID int64
|
||||
if v, ok := args["group_id"].(float64); ok {
|
||||
groupID = int64(v)
|
||||
}
|
||||
var displayOrder int
|
||||
if v, ok := args["display_order"].(float64); ok {
|
||||
displayOrder = int(v)
|
||||
}
|
||||
|
||||
pinned := &organization.OrgPinnedRepo{
|
||||
OrgID: org.ID,
|
||||
RepoID: repo.ID,
|
||||
GroupID: groupID,
|
||||
DisplayOrder: displayOrder,
|
||||
}
|
||||
|
||||
if err := organization.CreateOrgPinnedRepo(ctx, pinned); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"status": "pinned",
|
||||
"org": orgName,
|
||||
"repo": repoName,
|
||||
"id": pinned.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toolUnpinOrgRepo(ctx *context_service.APIContext, args map[string]any) (any, error) {
|
||||
orgName, _ := args["org"].(string)
|
||||
repoName, _ := args["repo"].(string)
|
||||
if orgName == "" || repoName == "" {
|
||||
return nil, errors.New("org and repo are required")
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("organization not found: %s", orgName)
|
||||
}
|
||||
|
||||
// Permission check
|
||||
if ctx.Doer == nil {
|
||||
return nil, errors.New("authentication required")
|
||||
}
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isOwner, err := org.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isOwner {
|
||||
return nil, errors.New("organization admin access required")
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, org.ID, repoName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repository not found: %s/%s", orgName, repoName)
|
||||
}
|
||||
|
||||
if err := organization.DeleteOrgPinnedRepo(ctx, org.ID, repo.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"status": "unpinned",
|
||||
"org": orgName,
|
||||
"repo": repoName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func repoLanguage(repo *repo_model.Repository) string {
|
||||
if repo.PrimaryLanguage != nil {
|
||||
return repo.PrimaryLanguage.Language
|
||||
}
|
||||
return ""
|
||||
}
|
||||
794
routers/api/v2/mcp_pages.go
Normal file
794
routers/api/v2/mcp_pages.go
Normal file
@@ -0,0 +1,794 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
user_model "code.gitcaddy.com/server/v3/models/user"
|
||||
"code.gitcaddy.com/server/v3/modules/json"
|
||||
pages_module "code.gitcaddy.com/server/v3/modules/pages"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
pages_service "code.gitcaddy.com/server/v3/services/pages"
|
||||
)
|
||||
|
||||
// Landing Pages MCP Tools
|
||||
var mcpPagesTools = []MCPTool{
|
||||
{
|
||||
Name: "get_landing_config",
|
||||
Description: "Get the full landing page configuration for a repository. Returns all sections: brand, hero, pricing, comparison, features, social proof, SEO, and more.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "list_landing_templates",
|
||||
Description: "List available landing page templates with display names. Templates include: open-source-hero, saas-conversion, bold-marketing, developer-tool, and more.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "enable_landing_page",
|
||||
Description: "Enable or disable the landing page for a repository. Optionally set a template.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo", "enabled"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"enabled": map[string]any{"type": "boolean", "description": "Enable or disable"},
|
||||
"template": map[string]any{"type": "string", "description": "Template name (e.g., 'saas-conversion', 'open-source-hero')"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_brand",
|
||||
Description: "Update the brand section of a landing page: name, logo URL, tagline, favicon.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"name": map[string]any{"type": "string", "description": "Brand name"},
|
||||
"logo_url": map[string]any{"type": "string", "description": "Logo image URL"},
|
||||
"tagline": map[string]any{"type": "string", "description": "Brand tagline"},
|
||||
"favicon_url": map[string]any{"type": "string", "description": "Favicon URL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_hero",
|
||||
Description: "Update the hero section: headline, subheadline, CTA buttons, hero image or video URL.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"headline": map[string]any{"type": "string", "description": "Main headline"},
|
||||
"subheadline": map[string]any{"type": "string", "description": "Supporting text"},
|
||||
"image_url": map[string]any{"type": "string", "description": "Hero image URL"},
|
||||
"video_url": map[string]any{"type": "string", "description": "Hero video URL"},
|
||||
"primary_cta_label": map[string]any{"type": "string", "description": "Primary CTA button label"},
|
||||
"primary_cta_url": map[string]any{"type": "string", "description": "Primary CTA button URL"},
|
||||
"secondary_cta_label": map[string]any{"type": "string", "description": "Secondary CTA button label"},
|
||||
"secondary_cta_url": map[string]any{"type": "string", "description": "Secondary CTA button URL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_pricing",
|
||||
Description: "Update the pricing section with plans. Each plan has a name, price, period, feature list, CTA, and optional 'featured' flag.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo", "plans"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"headline": map[string]any{"type": "string", "description": "Pricing section headline"},
|
||||
"subheadline": map[string]any{"type": "string", "description": "Pricing section subheadline"},
|
||||
"plans": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{"type": "string"},
|
||||
"price": map[string]any{"type": "string"},
|
||||
"period": map[string]any{"type": "string"},
|
||||
"features": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
"cta": map[string]any{"type": "string"},
|
||||
"featured": map[string]any{"type": "boolean"},
|
||||
},
|
||||
},
|
||||
"description": "Array of pricing plans",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_comparison",
|
||||
Description: "Update the feature comparison matrix. Define columns (products/tiers) and groups of features with per-column values.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"enabled": map[string]any{"type": "boolean", "description": "Enable comparison section"},
|
||||
"headline": map[string]any{"type": "string", "description": "Section headline"},
|
||||
"subheadline": map[string]any{"type": "string", "description": "Section subheadline"},
|
||||
"columns": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{"type": "string"},
|
||||
"description": "Column headers (e.g., ['Free', 'Pro', 'Enterprise'])",
|
||||
},
|
||||
"groups": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{"type": "string"},
|
||||
"features": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{"type": "string"},
|
||||
"values": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Feature groups with per-column values",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_features",
|
||||
Description: "Update the features section with title, description, and optional icon/image for each feature.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo", "features"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"features": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"title": map[string]any{"type": "string"},
|
||||
"description": map[string]any{"type": "string"},
|
||||
"icon": map[string]any{"type": "string"},
|
||||
"image_url": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"description": "Array of features",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_social_proof",
|
||||
Description: "Update testimonials and client logos for social proof.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"logos": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{"type": "string"},
|
||||
"description": "Client/partner logo URLs",
|
||||
},
|
||||
"testimonials": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"quote": map[string]any{"type": "string"},
|
||||
"author": map[string]any{"type": "string"},
|
||||
"role": map[string]any{"type": "string"},
|
||||
"avatar": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"description": "Testimonials",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_seo",
|
||||
Description: "Update SEO metadata: title, description, keywords, Open Graph image, Twitter card settings.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"title": map[string]any{"type": "string", "description": "SEO title"},
|
||||
"description": map[string]any{"type": "string", "description": "SEO description"},
|
||||
"keywords": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "SEO keywords"},
|
||||
"og_image": map[string]any{"type": "string", "description": "Open Graph image URL"},
|
||||
"twitter_card": map[string]any{"type": "string", "description": "Twitter card type (summary, summary_large_image)"},
|
||||
"twitter_site": map[string]any{"type": "string", "description": "Twitter @handle"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_theme",
|
||||
Description: "Update the visual theme: primary color, accent color, light/dark/auto mode.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"primary_color": map[string]any{"type": "string", "description": "Primary brand color (hex, e.g., '#512BD4')"},
|
||||
"accent_color": map[string]any{"type": "string", "description": "Accent color (hex)"},
|
||||
"mode": map[string]any{"type": "string", "enum": []string{"light", "dark", "auto"}, "description": "Color mode"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_stats",
|
||||
Description: "Update the stats counters displayed on the landing page. Each stat has a value and label.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo", "stats"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"stats": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"value": map[string]any{"type": "string"},
|
||||
"label": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"description": "Array of stat counters (e.g., [{value: '15+', label: 'Tools'}])",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_value_props",
|
||||
Description: "Update the value propositions section. Each value prop has a title, description, and icon.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo", "value_props"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"value_props": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"title": map[string]any{"type": "string"},
|
||||
"description": map[string]any{"type": "string"},
|
||||
"icon": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"description": "Array of value propositions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update_landing_cta",
|
||||
Description: "Update the call-to-action section at the bottom of the page with headline, subheadline, and button.",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"required": []string{"owner", "repo"},
|
||||
"properties": map[string]any{
|
||||
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
||||
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
||||
"headline": map[string]any{"type": "string", "description": "CTA headline"},
|
||||
"subheadline": map[string]any{"type": "string", "description": "CTA subheadline"},
|
||||
"button_label": map[string]any{"type": "string", "description": "Button text"},
|
||||
"button_url": map[string]any{"type": "string", "description": "Button URL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ── Tool Implementations ──────────────────────────────────
|
||||
|
||||
func toolGetLandingConfig(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
owner, repo, err := resolveOwnerRepo(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := pages_service.GetPagesConfig(ctx, repoObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get pages config: %w", err)
|
||||
}
|
||||
if config == nil {
|
||||
return map[string]any{"enabled": false, "message": "No landing page configured"}, nil
|
||||
}
|
||||
|
||||
return buildFullResponse(config), nil
|
||||
}
|
||||
|
||||
func toolListLandingTemplates(_ *context.APIContext, _ map[string]any) (any, error) { //nolint:unparam // signature must match tool handler type
|
||||
templates := pages_module.ValidTemplates()
|
||||
displayNames := pages_module.TemplateDisplayNames()
|
||||
|
||||
result := make([]map[string]string, 0, len(templates))
|
||||
for _, t := range templates {
|
||||
result = append(result, map[string]string{
|
||||
"id": t,
|
||||
"name": displayNames[t],
|
||||
})
|
||||
}
|
||||
return map[string]any{"templates": result}, nil
|
||||
}
|
||||
|
||||
func toolEnableLandingPage(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
owner, repo, err := resolveOwnerRepo(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabled, _ := args["enabled"].(bool)
|
||||
template, _ := args["template"].(string)
|
||||
|
||||
if enabled {
|
||||
if template == "" {
|
||||
template = "open-source-hero"
|
||||
}
|
||||
if !pages_module.IsValidTemplate(template) {
|
||||
return nil, fmt.Errorf("invalid template: %s", template)
|
||||
}
|
||||
if err := pages_service.EnablePages(ctx, repoObj, template); err != nil {
|
||||
return nil, fmt.Errorf("enable pages: %w", err)
|
||||
}
|
||||
return map[string]any{"enabled": true, "template": template}, nil
|
||||
}
|
||||
|
||||
if err := pages_service.DisablePages(ctx, repoObj); err != nil {
|
||||
return nil, fmt.Errorf("disable pages: %w", err)
|
||||
}
|
||||
return map[string]any{"enabled": false}, nil
|
||||
}
|
||||
|
||||
func toolUpdateLandingBrand(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["name"].(string); ok {
|
||||
config.Brand.Name = v
|
||||
}
|
||||
if v, ok := args["logo_url"].(string); ok {
|
||||
config.Brand.LogoURL = v
|
||||
}
|
||||
if v, ok := args["tagline"].(string); ok {
|
||||
config.Brand.Tagline = v
|
||||
}
|
||||
if v, ok := args["favicon_url"].(string); ok {
|
||||
config.Brand.FaviconURL = v
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "brand")
|
||||
}
|
||||
|
||||
func toolUpdateLandingHero(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["headline"].(string); ok {
|
||||
config.Hero.Headline = v
|
||||
}
|
||||
if v, ok := args["subheadline"].(string); ok {
|
||||
config.Hero.Subheadline = v
|
||||
}
|
||||
if v, ok := args["image_url"].(string); ok {
|
||||
config.Hero.ImageURL = v
|
||||
}
|
||||
if v, ok := args["video_url"].(string); ok {
|
||||
config.Hero.VideoURL = v
|
||||
}
|
||||
if v, ok := args["primary_cta_label"].(string); ok {
|
||||
config.Hero.PrimaryCTA.Label = v
|
||||
}
|
||||
if v, ok := args["primary_cta_url"].(string); ok {
|
||||
config.Hero.PrimaryCTA.URL = v
|
||||
}
|
||||
if v, ok := args["secondary_cta_label"].(string); ok {
|
||||
config.Hero.SecondaryCTA.Label = v
|
||||
}
|
||||
if v, ok := args["secondary_cta_url"].(string); ok {
|
||||
config.Hero.SecondaryCTA.URL = v
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "hero")
|
||||
}
|
||||
|
||||
func toolUpdateLandingPricing(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["headline"].(string); ok {
|
||||
config.Pricing.Headline = v
|
||||
}
|
||||
if v, ok := args["subheadline"].(string); ok {
|
||||
config.Pricing.Subheadline = v
|
||||
}
|
||||
if plans, ok := args["plans"].([]any); ok {
|
||||
config.Pricing.Plans = nil
|
||||
for _, p := range plans {
|
||||
if pm, ok := p.(map[string]any); ok {
|
||||
plan := pages_module.PricingPlanConfig{
|
||||
Name: strVal(pm, "name"),
|
||||
Price: strVal(pm, "price"),
|
||||
Period: strVal(pm, "period"),
|
||||
CTA: strVal(pm, "cta"),
|
||||
Featured: boolVal(pm, "featured"),
|
||||
}
|
||||
if features, ok := pm["features"].([]any); ok {
|
||||
for _, f := range features {
|
||||
if s, ok := f.(string); ok {
|
||||
plan.Features = append(plan.Features, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
config.Pricing.Plans = append(config.Pricing.Plans, plan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "pricing")
|
||||
}
|
||||
|
||||
func toolUpdateLandingComparison(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["enabled"].(bool); ok {
|
||||
config.Comparison.Enabled = v
|
||||
}
|
||||
if v, ok := args["headline"].(string); ok {
|
||||
config.Comparison.Headline = v
|
||||
}
|
||||
if v, ok := args["subheadline"].(string); ok {
|
||||
config.Comparison.Subheadline = v
|
||||
}
|
||||
if cols, ok := args["columns"].([]any); ok {
|
||||
config.Comparison.Columns = nil
|
||||
for _, c := range cols {
|
||||
if s, ok := c.(string); ok {
|
||||
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{Name: s})
|
||||
} else if cm, ok := c.(map[string]any); ok {
|
||||
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{
|
||||
Name: strVal(cm, "name"),
|
||||
Highlight: boolVal(cm, "highlight"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if groups, ok := args["groups"].([]any); ok {
|
||||
config.Comparison.Groups = nil
|
||||
for _, g := range groups {
|
||||
if gm, ok := g.(map[string]any); ok {
|
||||
group := pages_module.ComparisonGroupConfig{Name: strVal(gm, "name")}
|
||||
if features, ok := gm["features"].([]any); ok {
|
||||
for _, f := range features {
|
||||
if fm, ok := f.(map[string]any); ok {
|
||||
feature := pages_module.ComparisonFeatureConfig{Name: strVal(fm, "name")}
|
||||
if vals, ok := fm["values"].([]any); ok {
|
||||
for _, v := range vals {
|
||||
if s, ok := v.(string); ok {
|
||||
feature.Values = append(feature.Values, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
group.Features = append(group.Features, feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
config.Comparison.Groups = append(config.Comparison.Groups, group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "comparison")
|
||||
}
|
||||
|
||||
func toolUpdateLandingFeatures(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if features, ok := args["features"].([]any); ok {
|
||||
config.Features = nil
|
||||
for _, f := range features {
|
||||
if fm, ok := f.(map[string]any); ok {
|
||||
config.Features = append(config.Features, pages_module.FeatureConfig{
|
||||
Title: strVal(fm, "title"),
|
||||
Description: strVal(fm, "description"),
|
||||
Icon: strVal(fm, "icon"),
|
||||
ImageURL: strVal(fm, "image_url"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "features")
|
||||
}
|
||||
|
||||
func toolUpdateLandingSocialProof(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if logos, ok := args["logos"].([]any); ok {
|
||||
config.SocialProof.Logos = nil
|
||||
for _, l := range logos {
|
||||
if s, ok := l.(string); ok {
|
||||
config.SocialProof.Logos = append(config.SocialProof.Logos, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if testimonials, ok := args["testimonials"].([]any); ok {
|
||||
config.SocialProof.Testimonials = nil
|
||||
for _, t := range testimonials {
|
||||
if tm, ok := t.(map[string]any); ok {
|
||||
config.SocialProof.Testimonials = append(config.SocialProof.Testimonials, pages_module.TestimonialConfig{
|
||||
Quote: strVal(tm, "quote"),
|
||||
Author: strVal(tm, "author"),
|
||||
Role: strVal(tm, "role"),
|
||||
Avatar: strVal(tm, "avatar"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "social_proof")
|
||||
}
|
||||
|
||||
func toolUpdateLandingSEO(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["title"].(string); ok {
|
||||
config.SEO.Title = v
|
||||
}
|
||||
if v, ok := args["description"].(string); ok {
|
||||
config.SEO.Description = v
|
||||
}
|
||||
if v, ok := args["og_image"].(string); ok {
|
||||
config.SEO.OGImage = v
|
||||
}
|
||||
if v, ok := args["twitter_card"].(string); ok {
|
||||
config.SEO.TwitterCard = v
|
||||
}
|
||||
if v, ok := args["twitter_site"].(string); ok {
|
||||
config.SEO.TwitterSite = v
|
||||
}
|
||||
if keywords, ok := args["keywords"].([]any); ok {
|
||||
config.SEO.Keywords = nil
|
||||
for _, k := range keywords {
|
||||
if s, ok := k.(string); ok {
|
||||
config.SEO.Keywords = append(config.SEO.Keywords, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "seo")
|
||||
}
|
||||
|
||||
func toolUpdateLandingTheme(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["primary_color"].(string); ok {
|
||||
config.Theme.PrimaryColor = v
|
||||
}
|
||||
if v, ok := args["accent_color"].(string); ok {
|
||||
config.Theme.AccentColor = v
|
||||
}
|
||||
if v, ok := args["mode"].(string); ok {
|
||||
config.Theme.Mode = v
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "theme")
|
||||
}
|
||||
|
||||
func toolUpdateLandingStats(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stats, ok := args["stats"].([]any); ok {
|
||||
config.Stats = nil
|
||||
for _, s := range stats {
|
||||
if sm, ok := s.(map[string]any); ok {
|
||||
config.Stats = append(config.Stats, pages_module.StatConfig{
|
||||
Value: strVal(sm, "value"),
|
||||
Label: strVal(sm, "label"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "stats")
|
||||
}
|
||||
|
||||
func toolUpdateLandingValueProps(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vps, ok := args["value_props"].([]any); ok {
|
||||
config.ValueProps = nil
|
||||
for _, v := range vps {
|
||||
if vm, ok := v.(map[string]any); ok {
|
||||
config.ValueProps = append(config.ValueProps, pages_module.ValuePropConfig{
|
||||
Title: strVal(vm, "title"),
|
||||
Description: strVal(vm, "description"),
|
||||
Icon: strVal(vm, "icon"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "value_props")
|
||||
}
|
||||
|
||||
func toolUpdateLandingCTA(ctx *context.APIContext, args map[string]any) (any, error) {
|
||||
config, repoObj, err := getConfigForUpdate(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v, ok := args["headline"].(string); ok {
|
||||
config.CTASection.Headline = v
|
||||
}
|
||||
if v, ok := args["subheadline"].(string); ok {
|
||||
config.CTASection.Subheadline = v
|
||||
}
|
||||
if v, ok := args["button_label"].(string); ok {
|
||||
config.CTASection.Button.Label = v
|
||||
}
|
||||
if v, ok := args["button_url"].(string); ok {
|
||||
config.CTASection.Button.URL = v
|
||||
}
|
||||
|
||||
return saveAndReturn(ctx, repoObj, config, "cta_section")
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────
|
||||
|
||||
func getConfigForUpdate(ctx *context.APIContext, args map[string]any) (*pages_module.LandingConfig, *repo_model.Repository, error) {
|
||||
owner, repo, err := resolveOwnerRepo(args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
config, err := pages_service.GetPagesConfig(ctx, repoObj)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get config: %w", err)
|
||||
}
|
||||
if config == nil {
|
||||
config = pages_module.DefaultConfig()
|
||||
}
|
||||
|
||||
return config, repoObj, nil
|
||||
}
|
||||
|
||||
func saveAndReturn(ctx *context.APIContext, repo *repo_model.Repository, config *pages_module.LandingConfig, section string) (any, error) {
|
||||
configJSON, _ := json.Marshal(config)
|
||||
hash := pages_module.HashConfig(configJSON)
|
||||
|
||||
existing, _ := repo_model.GetPagesConfigByRepoID(ctx, repo.ID)
|
||||
|
||||
if existing != nil {
|
||||
existing.ConfigJSON = string(configJSON)
|
||||
existing.ConfigHash = hash
|
||||
existing.Template = repo_model.PagesTemplate(config.Template)
|
||||
existing.Enabled = config.Enabled
|
||||
if err := repo_model.UpdatePagesConfig(ctx, existing); err != nil {
|
||||
return nil, fmt.Errorf("save config: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := repo_model.CreatePagesConfig(ctx, &repo_model.PagesConfig{
|
||||
RepoID: repo.ID,
|
||||
Enabled: config.Enabled,
|
||||
Template: repo_model.PagesTemplate(config.Template),
|
||||
ConfigJSON: string(configJSON),
|
||||
ConfigHash: hash,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("create config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"success": true,
|
||||
"section": section,
|
||||
"message": fmt.Sprintf("Updated %s section", section),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func strVal(m map[string]any, key string) string {
|
||||
if v, ok := m[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func boolVal(m map[string]any, key string) bool {
|
||||
if v, ok := m[key].(bool); ok {
|
||||
return v
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveOwnerRepo(args map[string]any) (string, string, error) {
|
||||
owner, _ := args["owner"].(string)
|
||||
repo, _ := args["repo"].(string)
|
||||
if owner == "" || repo == "" {
|
||||
return "", "", errors.New("owner and repo are required")
|
||||
}
|
||||
return owner, repo, nil
|
||||
}
|
||||
|
||||
func getRepoByOwnerAndName(ctx *context.APIContext, owner, repo string) (*repo_model.Repository, error) {
|
||||
ownerObj, err := user_model.GetUserByName(ctx, owner)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("owner not found: %s", owner)
|
||||
}
|
||||
repoObj, err := repo_model.GetRepositoryByName(ctx, ownerObj.ID, repo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repo not found: %s/%s", owner, repo)
|
||||
}
|
||||
return repoObj, nil
|
||||
}
|
||||
526
routers/api/v2/org_overview.go
Normal file
526
routers/api/v2/org_overview.go
Normal file
@@ -0,0 +1,526 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/db"
|
||||
"code.gitcaddy.com/server/v3/models/organization"
|
||||
"code.gitcaddy.com/server/v3/models/perm"
|
||||
access_model "code.gitcaddy.com/server/v3/models/perm/access"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
apierrors "code.gitcaddy.com/server/v3/modules/errors"
|
||||
"code.gitcaddy.com/server/v3/modules/optional"
|
||||
api "code.gitcaddy.com/server/v3/modules/structs"
|
||||
"code.gitcaddy.com/server/v3/modules/web"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
"code.gitcaddy.com/server/v3/services/convert"
|
||||
org_service "code.gitcaddy.com/server/v3/services/org"
|
||||
user_service "code.gitcaddy.com/server/v3/services/user"
|
||||
)
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
// loadOrg looks up the org from the path param and checks it exists.
|
||||
func loadOrg(ctx *context.APIContext) *organization.Organization {
|
||||
orgName := ctx.PathParam("org")
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
ctx.APIErrorWithCode(apierrors.OrgNotFound)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return org
|
||||
}
|
||||
|
||||
// requireOrgOwner checks the doer is an org owner or site admin. Returns false if denied.
|
||||
func requireOrgOwner(ctx *context.APIContext, org *organization.Organization) bool {
|
||||
if ctx.Doer.IsAdmin {
|
||||
return true
|
||||
}
|
||||
isOwner, err := org.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return false
|
||||
}
|
||||
if !isOwner {
|
||||
ctx.APIErrorWithCode(apierrors.PermOrgAdminRequired)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// --- List user's orgs ---
|
||||
|
||||
// ListUserOrgsV2 returns all organizations the authenticated user belongs to.
|
||||
func ListUserOrgsV2(ctx *context.APIContext) {
|
||||
opts := organization.FindOrgOptions{
|
||||
ListOptions: db.ListOptions{PageSize: 50},
|
||||
UserID: ctx.Doer.ID,
|
||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.Doer),
|
||||
}
|
||||
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiOrgs := make([]*api.Organization, len(orgs))
|
||||
for i := range orgs {
|
||||
apiOrgs[i] = convert.ToOrganization(ctx, orgs[i])
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, apiOrgs)
|
||||
}
|
||||
|
||||
// --- Org Overview ---
|
||||
|
||||
// GetOrgOverviewV2 returns the full organization overview including profile readme and recent activity.
|
||||
func GetOrgOverviewV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Pinned repos
|
||||
pinnedRepos, err := org_service.GetOrgPinnedReposWithDetails(ctx, org.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Pinned groups
|
||||
pinnedGroups, err := organization.GetOrgPinnedGroups(ctx, org.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Public members
|
||||
publicMembers, totalMembers, err := organization.GetPublicOrgMembers(ctx, org.ID, 12)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Stats
|
||||
stats, err := org_service.GetOrgOverviewStats(ctx, org.ID, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Profile readme
|
||||
readme, _ := org_service.GetOrgProfileReadme(ctx, org.ID)
|
||||
|
||||
// Recent activity
|
||||
recentActivity, _ := org_service.GetOrgRecentActivity(ctx, org.ID, ctx.Doer, 10)
|
||||
|
||||
// Convert pinned repos
|
||||
apiPinnedRepos := make([]*api.OrgPinnedRepo, 0, len(pinnedRepos))
|
||||
for _, p := range pinnedRepos {
|
||||
if p.Repo == nil {
|
||||
continue
|
||||
}
|
||||
apiPinnedRepos = append(apiPinnedRepos, convertOrgPinnedRepoV2(ctx, p))
|
||||
}
|
||||
|
||||
apiPinnedGroups := make([]*api.OrgPinnedGroup, 0, len(pinnedGroups))
|
||||
for _, g := range pinnedGroups {
|
||||
apiPinnedGroups = append(apiPinnedGroups, convertOrgPinnedGroupV2(g))
|
||||
}
|
||||
|
||||
apiPublicMembers := make([]*api.OrgPublicMember, 0, len(publicMembers))
|
||||
for _, m := range publicMembers {
|
||||
apiPublicMembers = append(apiPublicMembers, &api.OrgPublicMember{
|
||||
User: convert.ToUser(ctx, m.User, ctx.Doer),
|
||||
Role: m.Role,
|
||||
})
|
||||
}
|
||||
|
||||
// Build profile content
|
||||
var profile *api.OrgProfileContent
|
||||
if readme != "" {
|
||||
profile = &api.OrgProfileContent{
|
||||
HasProfile: true,
|
||||
Readme: readme,
|
||||
}
|
||||
}
|
||||
|
||||
// Build recent activity
|
||||
var apiRecentActivity []api.OrgRecentActivity
|
||||
if len(recentActivity) > 0 {
|
||||
apiRecentActivity = make([]api.OrgRecentActivity, 0, len(recentActivity))
|
||||
for _, a := range recentActivity {
|
||||
apiRecentActivity = append(apiRecentActivity, api.OrgRecentActivity{
|
||||
RepoName: a.RepoName,
|
||||
RepoFullName: a.RepoFullName,
|
||||
DefaultBranch: a.DefaultBranch,
|
||||
CommitMessage: a.CommitMessage,
|
||||
CommitTime: a.CommitTime,
|
||||
IsPrivate: a.IsPrivate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
overview := &api.OrgOverviewV2{
|
||||
Organization: convert.ToOrganization(ctx, org),
|
||||
PinnedRepos: apiPinnedRepos,
|
||||
PinnedGroups: apiPinnedGroups,
|
||||
PublicMembers: apiPublicMembers,
|
||||
TotalMembers: totalMembers,
|
||||
Stats: &api.OrgOverviewStats{
|
||||
TotalRepos: stats.TotalRepos,
|
||||
TotalMembers: stats.TotalMembers,
|
||||
TotalTeams: stats.TotalTeams,
|
||||
TotalStars: stats.TotalStars,
|
||||
},
|
||||
Profile: profile,
|
||||
RecentActivity: apiRecentActivity,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, overview)
|
||||
}
|
||||
|
||||
// --- Update Org ---
|
||||
|
||||
// UpdateOrgV2Option represents the fields that can be updated on an org.
|
||||
type UpdateOrgV2Option struct {
|
||||
FullName *string `json:"full_name"`
|
||||
Email *string `json:"email"`
|
||||
Description *string `json:"description"`
|
||||
Website *string `json:"website"`
|
||||
Location *string `json:"location"`
|
||||
GroupHeader *string `json:"group_header"`
|
||||
}
|
||||
|
||||
// UpdateOrgV2 updates an organization's basic information.
|
||||
func UpdateOrgV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
if !requireOrgOwner(ctx, org) {
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*UpdateOrgV2Option)
|
||||
|
||||
opts := &user_service.UpdateOptions{}
|
||||
if form.FullName != nil {
|
||||
opts.FullName = optional.Some(*form.FullName)
|
||||
}
|
||||
if form.Description != nil {
|
||||
opts.Description = optional.Some(*form.Description)
|
||||
}
|
||||
if form.Website != nil {
|
||||
opts.Website = optional.Some(*form.Website)
|
||||
}
|
||||
if form.Location != nil {
|
||||
opts.Location = optional.Some(*form.Location)
|
||||
}
|
||||
if form.GroupHeader != nil {
|
||||
opts.GroupHeader = optional.Some(*form.GroupHeader)
|
||||
}
|
||||
|
||||
if form.Email != nil && *form.Email != "" {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), *form.Email); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Reload to get updated fields
|
||||
org, err := organization.GetOrgByName(ctx, org.Name)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
// --- List Org Repos ---
|
||||
|
||||
// ListOrgReposV2 lists all repos for an org, with optional group_header grouping.
|
||||
func ListOrgReposV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
limit := ctx.FormInt("limit")
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
groupBy := ctx.FormString("group_by")
|
||||
keyword := ctx.FormTrim("q")
|
||||
sortOrder := ctx.FormString("sort")
|
||||
|
||||
var orderBy db.SearchOrderBy
|
||||
switch sortOrder {
|
||||
case "newest":
|
||||
orderBy = db.SearchOrderByNewest
|
||||
case "oldest":
|
||||
orderBy = db.SearchOrderByOldest
|
||||
case "reversealphabetically":
|
||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
||||
case "stars":
|
||||
orderBy = db.SearchOrderByStarsReverse
|
||||
case "forks":
|
||||
orderBy = db.SearchOrderByForksReverse
|
||||
case "recentupdate":
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
default:
|
||||
orderBy = db.SearchOrderByAlphabetically
|
||||
}
|
||||
|
||||
// When grouping by group_header, prepend it to the sort
|
||||
if groupBy == "group_header" {
|
||||
orderBy = db.SearchOrderBy("CASE WHEN group_header = '' OR group_header IS NULL THEN 1 ELSE 0 END, group_header ASC, " + string(orderBy))
|
||||
}
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{PageSize: limit, Page: page},
|
||||
Keyword: keyword,
|
||||
OwnerID: org.ID,
|
||||
OrderBy: orderBy,
|
||||
Private: ctx.IsSigned,
|
||||
Actor: ctx.Doer,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeRead}))
|
||||
}
|
||||
|
||||
if groupBy == "group_header" {
|
||||
// Return grouped response
|
||||
type groupedEntry struct {
|
||||
GroupHeader string `json:"group_header"`
|
||||
Repos []*api.Repository `json:"repos"`
|
||||
}
|
||||
grouped := make(map[string][]*api.Repository)
|
||||
var headers []string
|
||||
headerSeen := make(map[string]bool)
|
||||
|
||||
for i, repo := range repos {
|
||||
header := repo.GroupHeader
|
||||
if !headerSeen[header] {
|
||||
headerSeen[header] = true
|
||||
headers = append(headers, header)
|
||||
}
|
||||
grouped[header] = append(grouped[header], apiRepos[i])
|
||||
}
|
||||
|
||||
groups := make([]groupedEntry, 0, len(headers))
|
||||
for _, h := range headers {
|
||||
groups = append(groups, groupedEntry{
|
||||
GroupHeader: h,
|
||||
Repos: grouped[h],
|
||||
})
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"groups": groups,
|
||||
"total": count,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiRepos)
|
||||
}
|
||||
|
||||
// --- Profile Readme ---
|
||||
|
||||
// GetOrgProfileReadmeV2 returns the raw README content from the .profile repo.
|
||||
func GetOrgProfileReadmeV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
|
||||
readme, err := org_service.GetOrgProfileReadme(ctx, org.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"content": readme,
|
||||
"has_profile": readme != "",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateOrgProfileReadmeOption represents a request to update the profile readme.
|
||||
type UpdateOrgProfileReadmeOption struct {
|
||||
Content string `json:"content" binding:"Required"`
|
||||
CommitMessage string `json:"commit_message"`
|
||||
}
|
||||
|
||||
// UpdateOrgProfileReadmeV2 updates the README.md in the .profile repo.
|
||||
func UpdateOrgProfileReadmeV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
if !requireOrgOwner(ctx, org) {
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*UpdateOrgProfileReadmeOption)
|
||||
|
||||
if err := org_service.UpdateOrgProfileReadme(ctx, ctx.Doer, org.ID, form.Content, form.CommitMessage); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
// --- Pinned Repos ---
|
||||
|
||||
// PinOrgRepoV2Option represents a request to pin a repo.
|
||||
type PinOrgRepoV2Option struct {
|
||||
RepoName string `json:"repo_name" binding:"Required"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
}
|
||||
|
||||
// PinOrgRepoV2 pins a repository to the org overview.
|
||||
func PinOrgRepoV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
if !requireOrgOwner(ctx, org) {
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*PinOrgRepoV2Option)
|
||||
|
||||
// Look up the repo
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, org.ID, form.RepoName)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, "Repository not found: "+form.RepoName)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if already pinned
|
||||
isPinned, err := organization.IsRepoPinned(ctx, org.ID, repo.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if isPinned {
|
||||
ctx.JSON(http.StatusOK, map[string]any{"status": "already_pinned"})
|
||||
return
|
||||
}
|
||||
|
||||
pinned := &organization.OrgPinnedRepo{
|
||||
OrgID: org.ID,
|
||||
RepoID: repo.ID,
|
||||
GroupID: form.GroupID,
|
||||
DisplayOrder: form.DisplayOrder,
|
||||
}
|
||||
|
||||
if err := organization.CreateOrgPinnedRepo(ctx, pinned); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusCreated, map[string]any{
|
||||
"id": pinned.ID,
|
||||
"repo_id": repo.ID,
|
||||
"status": "pinned",
|
||||
})
|
||||
}
|
||||
|
||||
// UnpinOrgRepoV2 unpins a repository from the org overview.
|
||||
func UnpinOrgRepoV2(ctx *context.APIContext) {
|
||||
org := loadOrg(ctx)
|
||||
if org == nil {
|
||||
return
|
||||
}
|
||||
if !requireOrgOwner(ctx, org) {
|
||||
return
|
||||
}
|
||||
|
||||
repoName := ctx.PathParam("repo")
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, org.ID, repoName)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, "Repository not found: "+repoName)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := organization.DeleteOrgPinnedRepo(ctx, org.ID, repo.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// --- Converters ---
|
||||
|
||||
func convertOrgPinnedRepoV2(ctx *context.APIContext, p *organization.OrgPinnedRepo) *api.OrgPinnedRepo {
|
||||
result := &api.OrgPinnedRepo{
|
||||
ID: p.ID,
|
||||
RepoID: p.RepoID,
|
||||
GroupID: p.GroupID,
|
||||
DisplayOrder: p.DisplayOrder,
|
||||
}
|
||||
|
||||
if p.Repo != nil {
|
||||
if repo, ok := p.Repo.(*repo_model.Repository); ok {
|
||||
result.Repo = convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeRead})
|
||||
}
|
||||
}
|
||||
|
||||
if p.Group != nil {
|
||||
result.Group = convertOrgPinnedGroupV2(p.Group)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func convertOrgPinnedGroupV2(g *organization.OrgPinnedGroup) *api.OrgPinnedGroup {
|
||||
return &api.OrgPinnedGroup{
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
DisplayOrder: g.DisplayOrder,
|
||||
Collapsed: g.Collapsed,
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@ func CheckAppUpdate(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
latestRelease.Repo = repo
|
||||
|
||||
// Find the appropriate asset for this platform/arch
|
||||
downloadURL, platformInfo := findUpdateAsset(latestRelease, platform, arch)
|
||||
@@ -346,6 +347,7 @@ func ListReleasesV2(ctx *context.APIContext) {
|
||||
// Convert to API format
|
||||
apiReleases := make([]*api.Release, 0, len(releases))
|
||||
for _, release := range releases {
|
||||
release.Repo = repo
|
||||
apiReleases = append(apiReleases, convertToAPIRelease(repo, release))
|
||||
}
|
||||
|
||||
@@ -388,6 +390,7 @@ func GetReleaseV2(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
release.Repo = repo
|
||||
ctx.JSON(http.StatusOK, convertToAPIRelease(repo, release))
|
||||
}
|
||||
|
||||
@@ -436,6 +439,7 @@ func GetLatestReleaseV2(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
release.Repo = repo
|
||||
ctx.JSON(http.StatusOK, convertToAPIRelease(repo, release))
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,12 @@ func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, t
|
||||
}
|
||||
|
||||
opts.Keyword = ctx.FormTrim("q")
|
||||
opts.OrderBy = orderBy
|
||||
// When grouping is enabled, order by group first so groups aren't split across pages
|
||||
if ctx.Data["PageIsExploreOrganizations"] == true && ctx.Data["ShowGrouping"] == true {
|
||||
opts.OrderBy = "CASE WHEN `user`.group_header = '' OR `user`.group_header IS NULL THEN 1 ELSE 0 END, `user`.group_header ASC, " + orderBy
|
||||
} else {
|
||||
opts.OrderBy = orderBy
|
||||
}
|
||||
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
|
||||
users, count, err = user_model.SearchUsers(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -76,6 +76,13 @@ func ServeLandingPage(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle social preview image before static routes so a catch-all
|
||||
// like /*.png cannot shadow the auto-generated OG card.
|
||||
if requestPath == "/social-preview.png" || requestPath == "/social-preview" {
|
||||
serveSocialPreview(ctx, repo)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for static route file serving
|
||||
if len(config.Advanced.StaticRoutes) > 0 {
|
||||
cleanPath := path.Clean(requestPath)
|
||||
@@ -91,12 +98,6 @@ func ServeLandingPage(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle social preview image
|
||||
if requestPath == "/social-preview.png" || requestPath == "/social-preview" {
|
||||
serveSocialPreview(ctx, repo)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle asset requests (gallery images, custom assets)
|
||||
// Uses /pages/assets/ to avoid conflict with Gitea's static /assets/ route
|
||||
if assetPath, found := strings.CutPrefix(requestPath, "/pages/assets/"); found && assetPath != "" {
|
||||
@@ -274,6 +275,85 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
|
||||
}
|
||||
}
|
||||
|
||||
// Load cross-promoted repos if enabled, filtered to those with landing pages
|
||||
if config.CrossPromote.Enabled {
|
||||
records, err := repo_model.GetCrossPromotedRepos(ctx, repo.ID)
|
||||
if err != nil {
|
||||
log.Warn("GetCrossPromotedRepos for repo %d: %v", repo.ID, err)
|
||||
} else if len(records) > 0 {
|
||||
repoIDs := make([]int64, len(records))
|
||||
for i, r := range records {
|
||||
repoIDs[i] = r.TargetRepoID
|
||||
}
|
||||
repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs)
|
||||
if err != nil {
|
||||
log.Warn("GetRepositoriesMapByIDs for cross-promote: %v", err)
|
||||
} else {
|
||||
type CrossPromoteItem struct {
|
||||
Name string
|
||||
Description string
|
||||
URL string
|
||||
LogoURL string
|
||||
}
|
||||
var items []CrossPromoteItem
|
||||
for _, r := range records {
|
||||
target, ok := repos[r.TargetRepoID]
|
||||
if !ok {
|
||||
log.Trace("Cross-promote target repo %d not found", r.TargetRepoID)
|
||||
continue
|
||||
}
|
||||
// Only require that the target repo has landing pages enabled —
|
||||
// the landing page is publicly reachable independent of the repo's
|
||||
// private flag, so we don't filter by target.IsPrivate.
|
||||
pagesEnabled, err := repo_model.IsPagesEnabled(ctx, target.ID)
|
||||
if err != nil || !pagesEnabled {
|
||||
log.Trace("Cross-promote target %s does not have pages enabled (err=%v, enabled=%v)", target.FullName(), err, pagesEnabled)
|
||||
continue
|
||||
}
|
||||
item := CrossPromoteItem{
|
||||
Description: target.Description,
|
||||
Name: target.Name,
|
||||
}
|
||||
// URL priority:
|
||||
// 1. Repo's explicit Website field (dev-controlled)
|
||||
// 2. Verified custom domain for pages
|
||||
// 3. Internal /pages URL
|
||||
switch {
|
||||
case target.Website != "":
|
||||
item.URL = target.Website
|
||||
default:
|
||||
item.URL = target.Link() + "/pages"
|
||||
if domains, err := repo_model.GetPagesDomainsByRepoID(ctx, target.ID); err == nil {
|
||||
for _, d := range domains {
|
||||
if d.Verified {
|
||||
item.URL = "https://" + d.Domain
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try to load brand info from pages config (optional)
|
||||
if targetConfig, err := pages_service.GetPagesConfig(ctx, target); err == nil && targetConfig != nil {
|
||||
if targetConfig.Brand.Name != "" {
|
||||
item.Name = targetConfig.Brand.Name
|
||||
}
|
||||
if targetConfig.Brand.LogoURL != "" {
|
||||
item.LogoURL = targetConfig.Brand.LogoURL
|
||||
} else if targetConfig.Brand.UploadedLogo != "" {
|
||||
item.LogoURL = "/repo-avatars/" + targetConfig.Brand.UploadedLogo
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
if len(items) > 0 {
|
||||
ctx.Data["CrossPromoteItems"] = items
|
||||
} else {
|
||||
log.Trace("Cross-promote enabled for repo %d but no eligible target repos", repo.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tpl := selectTemplate(config.Template)
|
||||
ctx.HTML(http.StatusOK, tpl)
|
||||
}
|
||||
@@ -1337,6 +1417,9 @@ func ensureTemplateDefaults(config *pages_module.LandingConfig) {
|
||||
if nav.LabelCompare == "" {
|
||||
nav.LabelCompare = defaults.LabelCompare
|
||||
}
|
||||
if nav.LabelCrossPromote == "" {
|
||||
nav.LabelCrossPromote = defaults.LabelCrossPromote
|
||||
}
|
||||
// Section headlines — fill empty headlines with sensible defaults
|
||||
// so they appear in the base JSON and can be overridden by translations.
|
||||
if config.Blog.Enabled && config.Blog.Headline == "" {
|
||||
@@ -1348,6 +1431,9 @@ func ensureTemplateDefaults(config *pages_module.LandingConfig) {
|
||||
if config.Comparison.Enabled && config.Comparison.Headline == "" {
|
||||
config.Comparison.Headline = "How We Compare"
|
||||
}
|
||||
if config.CrossPromote.Enabled && config.CrossPromote.Headline == "" {
|
||||
config.CrossPromote.Headline = "Related Offerings"
|
||||
}
|
||||
}
|
||||
|
||||
// ApproveExperiment handles the email approval link for an A/B test experiment
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
"code.gitcaddy.com/server/v3/modules/gitrepo"
|
||||
"code.gitcaddy.com/server/v3/modules/log"
|
||||
"code.gitcaddy.com/server/v3/modules/options"
|
||||
repo_module "code.gitcaddy.com/server/v3/modules/repository"
|
||||
"code.gitcaddy.com/server/v3/modules/templates"
|
||||
"code.gitcaddy.com/server/v3/services/context"
|
||||
@@ -43,6 +44,7 @@ var LicenseTypes = []LicenseCategory{
|
||||
{"Apache-2.0", "Apache License 2.0", "Like MIT, plus explicit patent protection"},
|
||||
{"BSD-2-Clause", "BSD 2-Clause", "Similar to MIT"},
|
||||
{"BSD-3-Clause", "BSD 3-Clause", "BSD with no-endorsement rule"},
|
||||
{"BSL-1.0", "Boost Software License 1.0", "Permissive, similar to MIT (often used for C++ libraries)"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -69,16 +71,31 @@ var LicenseTypes = []LicenseCategory{
|
||||
{
|
||||
Category: "Source-Available",
|
||||
Licenses: []LicenseInfo{
|
||||
{"BSL-1.1", "Business Source License 1.1", "Commercial use restricted until change date"},
|
||||
{"BSL-1.0", "Boost Software License", "Permissive, similar to MIT"},
|
||||
{"BUSL-1.1", "Business Source License 1.1", "Commercial use restricted until change date (auto-converts to Apache 2.0 after 4 years)"},
|
||||
{"SSPL-1.0", "Server Side Public License", "Requires publishing entire service stack"},
|
||||
{"ACL-1.0", "Auditable Commercial License 1.0", "Source-available with AI training restrictions; auto-converts to Apache 2.0 after 4 years"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: "Specifications",
|
||||
Licenses: []LicenseInfo{
|
||||
{"CSL-1.0", "Community Specification License 1.0", "For technical specifications. Creates LICENSE.md, GOVERNANCE.md, CONTRIBUTING.md, NOTICES.md bundle"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: "Public Domain",
|
||||
Licenses: []LicenseInfo{
|
||||
{"Unlicense", "Unlicense", "Public domain, no attribution required"},
|
||||
{"CC0-1.0", "CC0", "Public domain, internationally defensible"},
|
||||
{"CC0-1.0", "CC0 1.0 Universal", "Public domain dedication, internationally defensible"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: "Creative Commons (Documentation / Content)",
|
||||
Licenses: []LicenseInfo{
|
||||
{"CC-BY-4.0", "CC BY 4.0", "Attribution required. Most permissive CC license"},
|
||||
{"CC-BY-SA-4.0", "CC BY-SA 4.0", "Attribution + share-alike (like GPL for content)"},
|
||||
{"CC-BY-NC-4.0", "CC BY-NC 4.0", "Attribution + non-commercial use only"},
|
||||
{"CC-BY-ND-4.0", "CC BY-ND 4.0", "Attribution, no derivatives allowed"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -149,6 +166,16 @@ func LicenseCreate(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate license-specific required fields
|
||||
if licenseType == "ACL-1.0" {
|
||||
if strings.TrimSpace(ctx.FormString("acl_jurisdiction")) == "" ||
|
||||
strings.TrimSpace(ctx.FormString("acl_venue")) == "" {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.license_acl_required"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/license")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update repo license type
|
||||
repo.LicenseType = licenseType
|
||||
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "license_type"); err != nil {
|
||||
@@ -156,9 +183,19 @@ func LicenseCreate(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create LICENSE.md file
|
||||
if err := createLicenseFile(ctx, repo, licenseType); err != nil {
|
||||
log.Error("Failed to create LICENSE.md: %v", err)
|
||||
// Create license file(s)
|
||||
var err error
|
||||
switch licenseType {
|
||||
case "CSL-1.0":
|
||||
err = createCSLBundle(ctx, repo)
|
||||
case "ACL-1.0":
|
||||
err = createACLLicense(ctx, repo)
|
||||
default:
|
||||
err = createLicenseFile(ctx, repo, licenseType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("Failed to create license file(s) for %s: %v", licenseType, err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.license_file_error"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.license_created"))
|
||||
@@ -183,24 +220,91 @@ func createLicenseFile(ctx *context.Context, repo *repo_model.Repository, licens
|
||||
return fmt.Errorf("GetLicense: %w", err)
|
||||
}
|
||||
|
||||
// Create/update LICENSE.md using files service
|
||||
opts := &files_service.ChangeRepoFilesOptions{
|
||||
Message: fmt.Sprintf("Add LICENSE.md (%s)", licenseType),
|
||||
OldBranch: repo.DefaultBranch,
|
||||
NewBranch: repo.DefaultBranch,
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: "LICENSE.md",
|
||||
ContentReader: bytes.NewReader(licenseContent),
|
||||
},
|
||||
},
|
||||
return commitFiles(ctx, repo, fmt.Sprintf("Add LICENSE.md (%s)", licenseType), map[string][]byte{
|
||||
"LICENSE.md": licenseContent,
|
||||
})
|
||||
}
|
||||
|
||||
// createACLLicense generates a customized ACL-1.0 LICENSE.md with form-supplied
|
||||
// jurisdiction, venue, and optional fields substituted into the template.
|
||||
func createACLLicense(ctx *context.Context, repo *repo_model.Repository) error {
|
||||
raw, err := options.License("ACL-1.0")
|
||||
if err != nil {
|
||||
return fmt.Errorf("read ACL-1.0 template: %w", err)
|
||||
}
|
||||
|
||||
_, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts)
|
||||
ownerName := repo.OwnerDisplayName
|
||||
if ownerName == "" {
|
||||
ownerName = repo.OwnerName
|
||||
}
|
||||
jurisdiction := strings.TrimSpace(ctx.FormString("acl_jurisdiction"))
|
||||
venue := strings.TrimSpace(ctx.FormString("acl_venue"))
|
||||
contact := strings.TrimSpace(ctx.FormString("acl_contact"))
|
||||
licenseURL := strings.TrimSpace(ctx.FormString("acl_license_url"))
|
||||
tierURL := strings.TrimSpace(ctx.FormString("acl_tier_url"))
|
||||
year := time.Now().Format("2006")
|
||||
|
||||
content := string(raw)
|
||||
// Section 8.1 jurisdiction/venue
|
||||
content = strings.ReplaceAll(content, "[Jurisdiction]", jurisdiction)
|
||||
content = strings.ReplaceAll(content, "[Venue]", venue)
|
||||
// Attribution block placeholders
|
||||
content = strings.ReplaceAll(content, "[Year]", year)
|
||||
content = strings.ReplaceAll(content, "[Licensor Name]", ownerName)
|
||||
if licenseURL != "" {
|
||||
content = strings.ReplaceAll(content, "[URL to LICENSE.md]", licenseURL)
|
||||
}
|
||||
if tierURL != "" {
|
||||
content = strings.ReplaceAll(content, "[URL to Tier Schedule]", tierURL)
|
||||
}
|
||||
// Optional contact address — append a Notices section if provided
|
||||
if contact != "" {
|
||||
content += "\n## Notices\n\nLegal notices to the Licensor should be sent to: " + contact + "\n"
|
||||
}
|
||||
|
||||
return commitFiles(ctx, repo, "Add LICENSE.md (Auditable Commercial License v1.0)", map[string][]byte{
|
||||
"LICENSE.md": []byte(content),
|
||||
})
|
||||
}
|
||||
|
||||
// createCSLBundle creates the 4-file Community Specification License 1.0 bundle.
|
||||
func createCSLBundle(ctx *context.Context, repo *repo_model.Repository) error {
|
||||
files := map[string][]byte{}
|
||||
for _, name := range []string{"LICENSE.md", "GOVERNANCE.md", "CONTRIBUTING.md", "NOTICES.md"} {
|
||||
content, err := options.LicenseBundleFile("CSL-1.0", name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read CSL-1.0/%s: %w", name, err)
|
||||
}
|
||||
files[name] = content
|
||||
}
|
||||
return commitFiles(ctx, repo, "Add Community Specification License 1.0 bundle", files)
|
||||
}
|
||||
|
||||
// commitFiles creates or updates multiple files in a single commit. Falls back
|
||||
// to update if the file(s) already exist.
|
||||
func commitFiles(ctx *context.Context, repo *repo_model.Repository, message string, files map[string][]byte) error {
|
||||
build := func(operation string) []*files_service.ChangeRepoFile {
|
||||
out := make([]*files_service.ChangeRepoFile, 0, len(files))
|
||||
for path, content := range files {
|
||||
out = append(out, &files_service.ChangeRepoFile{
|
||||
Operation: operation,
|
||||
TreePath: path,
|
||||
ContentReader: bytes.NewReader(content),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
opts := &files_service.ChangeRepoFilesOptions{
|
||||
Message: message,
|
||||
OldBranch: repo.DefaultBranch,
|
||||
NewBranch: repo.DefaultBranch,
|
||||
Files: build("create"),
|
||||
}
|
||||
_, err := files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts)
|
||||
if err != nil {
|
||||
// If file already exists, try to update it instead
|
||||
opts.Files[0].Operation = "update"
|
||||
// Some files may already exist — retry as update
|
||||
opts.Files = build("update")
|
||||
_, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts)
|
||||
}
|
||||
return err
|
||||
@@ -258,21 +362,29 @@ func detectLicenseType(content string) string {
|
||||
|
||||
// Check for common license signatures
|
||||
licensePatterns := map[string][]string{
|
||||
"MIT": {"mit license", "permission is hereby granted, free of charge"},
|
||||
"Apache-2.0": {"apache license", "version 2.0"},
|
||||
"GPL-3.0": {"gnu general public license", "version 3"},
|
||||
"GPL-2.0": {"gnu general public license", "version 2"},
|
||||
"BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"},
|
||||
"BSD-2-Clause": {"redistribution and use in source and binary forms"},
|
||||
"LGPL-3.0": {"gnu lesser general public license", "version 3"},
|
||||
"LGPL-2.1": {"gnu lesser general public license", "version 2.1"},
|
||||
"MPL-2.0": {"mozilla public license", "version 2.0"},
|
||||
"AGPL-3.0": {"gnu affero general public license", "version 3"},
|
||||
"BSL-1.1": {"business source license", "change date"},
|
||||
"BSL-1.0": {"boost software license"},
|
||||
"Unlicense": {"this is free and unencumbered software", "unlicense"},
|
||||
"CC0-1.0": {"cc0 1.0 universal", "public domain"},
|
||||
"SSPL-1.0": {"server side public license"},
|
||||
"MIT": {"mit license", "permission is hereby granted, free of charge"},
|
||||
"Apache-2.0": {"apache license", "version 2.0"},
|
||||
"GPL-3.0": {"gnu general public license", "version 3"},
|
||||
"GPL-2.0": {"gnu general public license", "version 2"},
|
||||
"BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"},
|
||||
"BSD-2-Clause": {"redistribution and use in source and binary forms"},
|
||||
"LGPL-3.0": {"gnu lesser general public license", "version 3"},
|
||||
"LGPL-2.1": {"gnu lesser general public license", "version 2.1"},
|
||||
"MPL-2.0": {"mozilla public license", "version 2.0"},
|
||||
"AGPL-3.0": {"gnu affero general public license", "version 3"},
|
||||
"BUSL-1.1": {"business source license", "change date"},
|
||||
"BSL-1.0": {"boost software license"},
|
||||
"ACL-1.0": {"auditable commercial license"},
|
||||
"CSL-1.0": {"community specification license"},
|
||||
"Unlicense": {"this is free and unencumbered software", "unlicense"},
|
||||
"CC0-1.0": {"cc0 1.0 universal", "public domain"},
|
||||
"CC-BY-NC-ND-4.0": {"creative commons attribution-noncommercial-noderivatives"},
|
||||
"CC-BY-NC-SA-4.0": {"creative commons attribution-noncommercial-sharealike"},
|
||||
"CC-BY-NC-4.0": {"creative commons attribution-noncommercial 4.0"},
|
||||
"CC-BY-ND-4.0": {"creative commons attribution-noderivatives"},
|
||||
"CC-BY-SA-4.0": {"creative commons attribution-sharealike"},
|
||||
"CC-BY-4.0": {"creative commons attribution 4.0"},
|
||||
"SSPL-1.0": {"server side public license"},
|
||||
}
|
||||
|
||||
for license, patterns := range licensePatterns {
|
||||
|
||||
@@ -111,6 +111,9 @@ func applyTemplateDefaultLabels(config *pages_module.LandingConfig) {
|
||||
if nav.LabelCompare == "" {
|
||||
nav.LabelCompare = defaults.LabelCompare
|
||||
}
|
||||
if nav.LabelCrossPromote == "" {
|
||||
nav.LabelCrossPromote = defaults.LabelCrossPromote
|
||||
}
|
||||
}
|
||||
|
||||
// setCommonPagesData sets common data for all pages settings pages
|
||||
@@ -479,6 +482,12 @@ func PagesContentPost(ctx *context.Context) {
|
||||
config.Advanced.HideMobileReleases = ctx.FormBool("hide_mobile_releases")
|
||||
config.Advanced.GooglePlayID = strings.TrimSpace(ctx.FormString("google_play_id"))
|
||||
config.Advanced.AppStoreID = strings.TrimSpace(ctx.FormString("app_store_id"))
|
||||
if v := ctx.FormString("label_value_props"); v != "" {
|
||||
config.Navigation.LabelValueProps = v
|
||||
}
|
||||
if v := ctx.FormString("label_features"); v != "" {
|
||||
config.Navigation.LabelFeatures = v
|
||||
}
|
||||
config.Navigation.ShowDocs = ctx.FormBool("nav_show_docs")
|
||||
config.Navigation.ShowAPI = ctx.FormBool("nav_show_api")
|
||||
config.Navigation.ShowRepository = ctx.FormBool("nav_show_repository")
|
||||
@@ -508,6 +517,8 @@ func PagesContentPost(ctx *context.Context) {
|
||||
}
|
||||
config.Stats = append(config.Stats, pages_module.StatConfig{Value: value, Label: label})
|
||||
}
|
||||
config.ValuePropsHeadline = ctx.FormString("value_props_headline")
|
||||
config.ValuePropsSubheadline = ctx.FormString("value_props_subheadline")
|
||||
config.ValueProps = nil
|
||||
for i := range 10 {
|
||||
title := ctx.FormString(fmt.Sprintf("valueprop_title_%d", i))
|
||||
@@ -518,6 +529,8 @@ func PagesContentPost(ctx *context.Context) {
|
||||
}
|
||||
config.ValueProps = append(config.ValueProps, pages_module.ValuePropConfig{Title: title, Description: desc, Icon: icon})
|
||||
}
|
||||
config.FeaturesHeadline = ctx.FormString("features_headline")
|
||||
config.FeaturesSubheadline = ctx.FormString("features_subheadline")
|
||||
config.Features = nil
|
||||
for i := range 20 {
|
||||
title := ctx.FormString(fmt.Sprintf("feature_title_%d", i))
|
||||
@@ -531,6 +544,12 @@ func PagesContentPost(ctx *context.Context) {
|
||||
}
|
||||
// Comparison enabled toggle (full config is on /settings/pages/comparison)
|
||||
config.Comparison.Enabled = ctx.FormBool("comparison_enabled")
|
||||
// Cross-promote section
|
||||
config.CrossPromote.Enabled = ctx.FormBool("cross_promote_enabled")
|
||||
if v := ctx.FormString("cross_promote_headline"); v != "" {
|
||||
config.CrossPromote.Headline = v
|
||||
}
|
||||
config.CrossPromote.Subheadline = ctx.FormString("cross_promote_subheadline")
|
||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
||||
ctx.ServerError("SavePagesConfig", err)
|
||||
return
|
||||
@@ -778,6 +797,12 @@ type TranslationView struct {
|
||||
CTAHeadline string
|
||||
CTASubheadline string
|
||||
CTAButton string
|
||||
// Value Props section
|
||||
ValuePropsHeadline string
|
||||
ValuePropsSubheadline string
|
||||
// Features section
|
||||
FeaturesHeadline string
|
||||
FeaturesSubheadline string
|
||||
// Blog
|
||||
BlogHeadline string
|
||||
BlogSubheadline string
|
||||
@@ -788,6 +813,9 @@ type TranslationView struct {
|
||||
// Comparison
|
||||
ComparisonHeadline string
|
||||
ComparisonSubheadline string
|
||||
// Cross-Promote
|
||||
CrossPromoteHeadline string
|
||||
CrossPromoteSubheadline string
|
||||
// Footer
|
||||
FooterCopyright string
|
||||
FooterLinkLabels []string
|
||||
@@ -795,16 +823,17 @@ type TranslationView struct {
|
||||
SEOTitle string
|
||||
SEODescription string
|
||||
// Navigation labels
|
||||
NavLabelValueProps string
|
||||
NavLabelFeatures string
|
||||
NavLabelPricing string
|
||||
NavLabelBlog string
|
||||
NavLabelGallery string
|
||||
NavLabelCompare string
|
||||
NavLabelDocs string
|
||||
NavLabelReleases string
|
||||
NavLabelAPI string
|
||||
NavLabelIssues string
|
||||
NavLabelValueProps string
|
||||
NavLabelFeatures string
|
||||
NavLabelPricing string
|
||||
NavLabelBlog string
|
||||
NavLabelGallery string
|
||||
NavLabelCompare string
|
||||
NavLabelCrossPromote string
|
||||
NavLabelDocs string
|
||||
NavLabelReleases string
|
||||
NavLabelAPI string
|
||||
NavLabelIssues string
|
||||
}
|
||||
|
||||
// overlayString extracts a string from a map
|
||||
@@ -874,10 +903,14 @@ func parseTranslationView(t *pages_model.Translation, config *pages_module.Landi
|
||||
// Value Props
|
||||
view.ValuePropTitles = overlayStringSlice(overlay, "value_props", "title", len(config.ValueProps))
|
||||
view.ValuePropDescs = overlayStringSlice(overlay, "value_props", "description", len(config.ValueProps))
|
||||
view.ValuePropsHeadline = overlayString(overlay, "value_props_headline")
|
||||
view.ValuePropsSubheadline = overlayString(overlay, "value_props_subheadline")
|
||||
|
||||
// Features
|
||||
view.FeatureTitles = overlayStringSlice(overlay, "features", "title", len(config.Features))
|
||||
view.FeatureDescs = overlayStringSlice(overlay, "features", "description", len(config.Features))
|
||||
view.FeaturesHeadline = overlayString(overlay, "features_headline")
|
||||
view.FeaturesSubheadline = overlayString(overlay, "features_subheadline")
|
||||
|
||||
// Testimonials (stored under social_proof.testimonials)
|
||||
view.TestimonialQuotes = make([]string, len(config.SocialProof.Testimonials))
|
||||
@@ -955,6 +988,12 @@ func parseTranslationView(t *pages_model.Translation, config *pages_module.Landi
|
||||
view.ComparisonSubheadline = overlayString(comp, "subheadline")
|
||||
}
|
||||
|
||||
// Cross-Promote
|
||||
if cp, ok := overlay["cross_promote"].(map[string]any); ok {
|
||||
view.CrossPromoteHeadline = overlayString(cp, "headline")
|
||||
view.CrossPromoteSubheadline = overlayString(cp, "subheadline")
|
||||
}
|
||||
|
||||
// Footer
|
||||
view.FooterLinkLabels = make([]string, len(config.Footer.Links))
|
||||
if footer, ok := overlay["footer"].(map[string]any); ok {
|
||||
@@ -985,6 +1024,7 @@ func parseTranslationView(t *pages_model.Translation, config *pages_module.Landi
|
||||
view.NavLabelBlog = overlayString(nav, "label_blog")
|
||||
view.NavLabelGallery = overlayString(nav, "label_gallery")
|
||||
view.NavLabelCompare = overlayString(nav, "label_compare")
|
||||
view.NavLabelCrossPromote = overlayString(nav, "label_cross_promote")
|
||||
view.NavLabelDocs = overlayString(nav, "label_docs")
|
||||
view.NavLabelReleases = overlayString(nav, "label_releases")
|
||||
view.NavLabelAPI = overlayString(nav, "label_api")
|
||||
@@ -1062,6 +1102,12 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
if len(valueProps) > 0 {
|
||||
overlay["value_props"] = valueProps
|
||||
}
|
||||
if v := ctx.FormString("trans_value_props_headline"); v != "" {
|
||||
overlay["value_props_headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_value_props_subheadline"); v != "" {
|
||||
overlay["value_props_subheadline"] = v
|
||||
}
|
||||
|
||||
// Features (indexed)
|
||||
var features []map[string]any
|
||||
@@ -1079,6 +1125,12 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
if len(features) > 0 {
|
||||
overlay["features"] = features
|
||||
}
|
||||
if v := ctx.FormString("trans_features_headline"); v != "" {
|
||||
overlay["features_headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_features_subheadline"); v != "" {
|
||||
overlay["features_subheadline"] = v
|
||||
}
|
||||
|
||||
// Testimonials (indexed)
|
||||
var testimonials []map[string]any
|
||||
@@ -1178,6 +1230,18 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
overlay["comparison"] = comp
|
||||
}
|
||||
|
||||
// Cross-Promote
|
||||
cp := map[string]any{}
|
||||
if v := ctx.FormString("trans_cross_promote_headline"); v != "" {
|
||||
cp["headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_cross_promote_subheadline"); v != "" {
|
||||
cp["subheadline"] = v
|
||||
}
|
||||
if len(cp) > 0 {
|
||||
overlay["cross_promote"] = cp
|
||||
}
|
||||
|
||||
// Footer
|
||||
footer := map[string]any{}
|
||||
if v := ctx.FormString("trans_footer_copyright"); v != "" {
|
||||
@@ -1216,7 +1280,7 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
nav := map[string]any{}
|
||||
for _, key := range []string{
|
||||
"label_value_props", "label_features", "label_pricing",
|
||||
"label_blog", "label_gallery", "label_compare",
|
||||
"label_blog", "label_gallery", "label_compare", "label_cross_promote",
|
||||
"label_docs", "label_releases", "label_api", "label_issues",
|
||||
} {
|
||||
if v := ctx.FormString("trans_nav_" + key); v != "" {
|
||||
@@ -1533,10 +1597,6 @@ func PagesAdvancedPost(ctx *context.Context) {
|
||||
// Parse remaining fields
|
||||
config.Advanced.CustomCSS = ctx.FormString("custom_css")
|
||||
config.Advanced.CustomHead = ctx.FormString("custom_head")
|
||||
config.Advanced.GooglePlayID = ctx.FormString("google_play_id")
|
||||
config.Advanced.AppStoreID = ctx.FormString("app_store_id")
|
||||
config.Advanced.PublicReleases = ctx.FormBool("public_releases")
|
||||
config.Advanced.HideMobileReleases = ctx.FormBool("hide_mobile_releases")
|
||||
|
||||
if err := savePagesLandingConfig(ctx, config); err != nil {
|
||||
ctx.ServerError("SavePagesConfig", err)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"code.gitcaddy.com/server/v3/modules/setting"
|
||||
"code.gitcaddy.com/server/v3/modules/structs"
|
||||
"code.gitcaddy.com/server/v3/modules/templates"
|
||||
"code.gitcaddy.com/server/v3/modules/timeutil"
|
||||
"code.gitcaddy.com/server/v3/modules/util"
|
||||
"code.gitcaddy.com/server/v3/modules/validation"
|
||||
"code.gitcaddy.com/server/v3/modules/web"
|
||||
@@ -124,6 +125,12 @@ func SettingsPost(ctx *context.Context) {
|
||||
handleSettingsPostPushMirrorRemove(ctx)
|
||||
case "push-mirror-add":
|
||||
handleSettingsPostPushMirrorAdd(ctx)
|
||||
case "push-mirror-reset":
|
||||
handleSettingsPostPushMirrorReset(ctx)
|
||||
case "push-mirror-reset-all":
|
||||
handleSettingsPostPushMirrorResetAll(ctx)
|
||||
case "repo-reset":
|
||||
handleSettingsPostRepoReset(ctx)
|
||||
case "advanced":
|
||||
handleSettingsPostAdvanced(ctx)
|
||||
case "signing":
|
||||
@@ -377,8 +384,9 @@ func handleSettingsPostPushMirrorUpdate(ctx *context.Context) {
|
||||
}
|
||||
|
||||
m.Interval = interval
|
||||
if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil {
|
||||
ctx.ServerError("UpdatePushMirrorInterval", err)
|
||||
m.ExcludeHiddenFiles = form.PushMirrorExcludeHiddenFiles
|
||||
if _, err := repo_model.UpdatePushMirrorCols(ctx, m, "interval", "exclude_hidden_files"); err != nil {
|
||||
ctx.ServerError("UpdatePushMirror", err)
|
||||
return
|
||||
}
|
||||
// Background why we are adding it to Queue
|
||||
@@ -470,12 +478,13 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) {
|
||||
}
|
||||
|
||||
m := &repo_model.PushMirror{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
RemoteName: "remote_mirror_" + remoteSuffix,
|
||||
SyncOnCommit: form.PushMirrorSyncOnCommit,
|
||||
Interval: interval,
|
||||
RemoteAddress: remoteAddress,
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
RemoteName: "remote_mirror_" + remoteSuffix,
|
||||
SyncOnCommit: form.PushMirrorSyncOnCommit,
|
||||
ExcludeHiddenFiles: form.PushMirrorExcludeHiddenFiles,
|
||||
Interval: interval,
|
||||
RemoteAddress: remoteAddress,
|
||||
}
|
||||
if err := db.Insert(ctx, m); err != nil {
|
||||
ctx.ServerError("InsertPushMirror", err)
|
||||
@@ -494,6 +503,179 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) {
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
}
|
||||
|
||||
func handleSettingsPostPushMirrorReset(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if !setting.Mirror.Enabled || repo.IsArchived {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
ctx.Data["Err_RepoName"] = nil
|
||||
|
||||
branch := strings.TrimSpace(form.PushMirrorResetBranch)
|
||||
commit := strings.TrimSpace(form.PushMirrorResetCommit)
|
||||
if branch == "" || commit == "" {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_required"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
m, _, _ := repo_model.GetPushMirrorByIDAndRepoID(ctx, form.PushMirrorID, repo.ID)
|
||||
if m == nil {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := mirror_service.ResetPushMirrorBranch(ctx, m, branch, commit); err != nil {
|
||||
log.Error("ResetPushMirrorBranch[%d]: %v", m.ID, err)
|
||||
m.LastError = err.Error()
|
||||
m.Interval = 0
|
||||
m.SyncOnCommit = false
|
||||
if _, saveErr := repo_model.UpdatePushMirrorCols(ctx, m, "interval", "sync_on_commit", "last_error"); saveErr != nil {
|
||||
log.Error("UpdatePushMirrorCols after reset failure: %v", saveErr)
|
||||
}
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_failed", err.Error()))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
// Reset succeeded — pause auto-sync so the next scheduled sync doesn't undo the reset.
|
||||
m.Interval = 0
|
||||
m.SyncOnCommit = false
|
||||
m.LastError = ""
|
||||
m.LastUpdateUnix = timeutil.TimeStampNow()
|
||||
if _, err := repo_model.UpdatePushMirrorCols(ctx, m, "interval", "sync_on_commit", "last_error", "last_update_unix"); err != nil {
|
||||
log.Error("UpdatePushMirrorCols after reset: %v", err)
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_success"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
}
|
||||
|
||||
func handleSettingsPostPushMirrorResetAll(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if !setting.Mirror.Enabled || repo.IsArchived {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
ctx.Data["Err_RepoName"] = nil
|
||||
|
||||
branch := strings.TrimSpace(form.PushMirrorResetBranch)
|
||||
commit := strings.TrimSpace(form.PushMirrorResetCommit)
|
||||
if branch == "" || commit == "" {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_required"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
successCount, errs := mirror_service.ResetAllPushMirrorsForRepo(ctx, repo, branch, commit)
|
||||
|
||||
// Pause auto-sync on every mirror regardless — partial success still means
|
||||
// the dev wanted to pause, and any retry should be deliberate.
|
||||
mirrors, _, _ := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{ListAll: true})
|
||||
for _, m := range mirrors {
|
||||
m.Interval = 0
|
||||
m.SyncOnCommit = false
|
||||
if successCount > 0 {
|
||||
m.LastUpdateUnix = timeutil.TimeStampNow()
|
||||
m.LastError = ""
|
||||
}
|
||||
_, _ = repo_model.UpdatePushMirrorCols(ctx, m, "interval", "sync_on_commit", "last_error", "last_update_unix")
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_all_success", successCount))
|
||||
} else {
|
||||
errMsgs := make([]string, 0, len(errs))
|
||||
for _, e := range errs {
|
||||
errMsgs = append(errMsgs, e.Error())
|
||||
}
|
||||
ctx.Flash.Warning(ctx.Tr("repo.settings.mirror_settings.push_mirror.reset_all_partial", successCount, len(errs), strings.Join(errMsgs, "; ")))
|
||||
}
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
}
|
||||
|
||||
func handleSettingsPostRepoReset(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if !ctx.Repo.IsOwner() && !ctx.Doer.IsAdmin {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
if repo.IsArchived || repo.IsEmpty {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
ctx.Data["Err_RepoName"] = nil
|
||||
|
||||
branch := strings.TrimSpace(form.RepoResetBranch)
|
||||
commit := strings.TrimSpace(form.RepoResetCommit)
|
||||
confirmName := strings.TrimSpace(form.RepoName)
|
||||
if branch == "" || commit == "" {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.repo_reset.required"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
if confirmName != repo.Name {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.repo_reset.confirm_mismatch"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
// Open the repo and verify the commit exists
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
if _, err := gitRepo.GetCommit(commit); err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.repo_reset.commit_not_found", commit))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
if _, err := gitRepo.GetBranchCommitID(branch); err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.repo_reset.branch_not_found", branch))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
// Move the local branch ref to the target commit. Old commits become
|
||||
// unreachable and will be collected by gc; this matches `git reset --hard`
|
||||
// from the user's perspective.
|
||||
if err := gitRepo.UpdateRef("refs/heads/"+branch, commit); err != nil {
|
||||
log.Error("UpdateRef[%s -> %s]: %v", branch, commit, err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.repo_reset.failed", err.Error()))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
// Force-push the new branch state to all push mirrors and pause auto-sync
|
||||
mirrors, _, _ := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{ListAll: true})
|
||||
mirrorErrs := []string{}
|
||||
for _, m := range mirrors {
|
||||
if err := mirror_service.ResetPushMirrorBranch(ctx, m, branch, commit); err != nil {
|
||||
log.Warn("ResetPushMirrorBranch[%d]: %v", m.ID, err)
|
||||
mirrorErrs = append(mirrorErrs, m.RemoteAddress+": "+err.Error())
|
||||
}
|
||||
m.Interval = 0
|
||||
m.SyncOnCommit = false
|
||||
m.LastUpdateUnix = timeutil.TimeStampNow()
|
||||
_, _ = repo_model.UpdatePushMirrorCols(ctx, m, "interval", "sync_on_commit", "last_update_unix")
|
||||
}
|
||||
|
||||
if len(mirrorErrs) == 0 {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.repo_reset.success"))
|
||||
} else {
|
||||
ctx.Flash.Warning(ctx.Tr("repo.settings.repo_reset.partial", strings.Join(mirrorErrs, "; ")))
|
||||
}
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
}
|
||||
|
||||
func newRepoUnit(repo *repo_model.Repository, unitType unit_model.Type, config convert.Conversion) repo_model.RepoUnit {
|
||||
repoUnit := repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, Config: config}
|
||||
for _, u := range repo.Units {
|
||||
|
||||
@@ -89,26 +89,31 @@ func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) bindi
|
||||
|
||||
// RepoSettingForm form for changing repository settings
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(2048)"`
|
||||
DisplayTitle string `binding:"MaxSize(255)"`
|
||||
GroupHeader string `binding:"MaxSize(255)"`
|
||||
OwnerDisplayName string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"ValidUrl;MaxSize(1024)"`
|
||||
Interval string
|
||||
MirrorAddress string
|
||||
MirrorUsername string
|
||||
MirrorPassword string
|
||||
LFS bool `form:"mirror_lfs"`
|
||||
LFSEndpoint string `form:"mirror_lfs_endpoint"`
|
||||
PushMirrorID int64
|
||||
PushMirrorAddress string
|
||||
PushMirrorUsername string
|
||||
PushMirrorPassword string
|
||||
PushMirrorSyncOnCommit bool
|
||||
PushMirrorInterval string
|
||||
Template bool
|
||||
EnablePrune bool
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(2048)"`
|
||||
DisplayTitle string `binding:"MaxSize(255)"`
|
||||
GroupHeader string `binding:"MaxSize(255)"`
|
||||
OwnerDisplayName string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"ValidUrl;MaxSize(1024)"`
|
||||
Interval string
|
||||
MirrorAddress string
|
||||
MirrorUsername string
|
||||
MirrorPassword string
|
||||
LFS bool `form:"mirror_lfs"`
|
||||
LFSEndpoint string `form:"mirror_lfs_endpoint"`
|
||||
PushMirrorID int64
|
||||
PushMirrorAddress string
|
||||
PushMirrorUsername string
|
||||
PushMirrorPassword string
|
||||
PushMirrorSyncOnCommit bool
|
||||
PushMirrorExcludeHiddenFiles bool
|
||||
PushMirrorInterval string
|
||||
PushMirrorResetBranch string
|
||||
PushMirrorResetCommit string
|
||||
RepoResetBranch string
|
||||
RepoResetCommit string
|
||||
Template bool
|
||||
EnablePrune bool
|
||||
|
||||
// Advanced settings
|
||||
EnableCode bool
|
||||
|
||||
@@ -28,6 +28,168 @@ import (
|
||||
|
||||
var stripExitStatus = regexp.MustCompile(`exit status \d+ - `)
|
||||
|
||||
// buildFilterOpts builds a FilterTreeOptions from the repo's hidden file settings.
|
||||
func buildFilterOpts(ctx context.Context, repo *repo_model.Repository) *git.FilterTreeOptions {
|
||||
opts := &git.FilterTreeOptions{
|
||||
ExcludeDotFiles: repo.HideDotfiles,
|
||||
}
|
||||
if hiddenPaths, err := repo_model.GetHiddenFolderPaths(ctx, repo.ID); err == nil && len(hiddenPaths) > 0 {
|
||||
opts.ExcludePaths = hiddenPaths
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// performFilteredPush pushes each branch and tag with dot folders/hidden paths stripped.
|
||||
// Uses git plumbing to build filtered commits (no refs created, no artifacts left behind).
|
||||
func performFilteredPush(ctx context.Context, repo *repo_model.Repository, remoteName string, filterOpts *git.FilterTreeOptions, timeout time.Duration) error {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open repository: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, repo, remoteName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get remote URL: %w", err)
|
||||
}
|
||||
envs := proxy.EnvWithProxy(remoteURL.URL)
|
||||
|
||||
// Use the resolved URL as the push target instead of the remote name.
|
||||
// Push mirrors are registered with `--mirror=push`, which sets
|
||||
// `remote.<name>.mirror = true` in git config. With that flag, `git push <name>`
|
||||
// is implicitly `--mirror`, which cannot be combined with refspecs. Pushing
|
||||
// to the URL directly bypasses the remote config and lets us use refspecs.
|
||||
pushTarget := remoteURL.String()
|
||||
|
||||
// Push each branch with filtered content
|
||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get branches: %w", err)
|
||||
}
|
||||
|
||||
for _, branch := range branches {
|
||||
commitID, err := gitRepo.GetBranchCommitID(branch)
|
||||
if err != nil {
|
||||
log.Warn("GetBranchCommitID(%s): %v", branch, err)
|
||||
continue
|
||||
}
|
||||
|
||||
filteredSHA, err := gitRepo.BuildFilteredCommit(commitID, filterOpts)
|
||||
if err != nil {
|
||||
log.Warn("BuildFilteredCommit(%s/%s): %v", branch, commitID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Trace("Push mirror filtered [repo: %-v] branch %s: %s -> %s", repo, branch, commitID[:12], filteredSHA[:12])
|
||||
|
||||
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
|
||||
Remote: pushTarget,
|
||||
Force: true,
|
||||
Branch: "refs/heads/" + branch,
|
||||
LocalRefName: filteredSHA,
|
||||
Timeout: timeout,
|
||||
Env: envs,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("push branch %s: %w", branch, util.SanitizeErrorCredentialURLs(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Push tags as-is (tags point at commits, not trees, so dot folders in the
|
||||
// tagged tree are acceptable — the tag itself has no file content).
|
||||
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
|
||||
Remote: pushTarget,
|
||||
Force: true,
|
||||
Branch: "refs/tags/*",
|
||||
LocalRefName: "refs/tags/*",
|
||||
Timeout: timeout,
|
||||
Env: envs,
|
||||
}); err != nil {
|
||||
// Non-fatal — tags may not exist or remote may reject
|
||||
log.Trace("Push mirror tags [repo: %-v]: %v", repo, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetPushMirrorBranch force-pushes a specific commit SHA to a branch on the
|
||||
// push mirror's remote. This is a one-time destructive operation: it rewrites
|
||||
// the branch on the remote regardless of what's there. The caller is expected
|
||||
// to pause auto-sync afterwards so a regular sync doesn't overwrite the reset.
|
||||
func ResetPushMirrorBranch(ctx context.Context, m *repo_model.PushMirror, branch, commitSHA string) error {
|
||||
if branch == "" || commitSHA == "" {
|
||||
return errors.New("branch and commit are required")
|
||||
}
|
||||
repo := m.GetRepository(ctx)
|
||||
if repo == nil {
|
||||
return errors.New("push mirror has no repository")
|
||||
}
|
||||
|
||||
// Verify the commit exists locally before pushing
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open repository: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
if _, err := gitRepo.GetCommit(commitSHA); err != nil {
|
||||
return fmt.Errorf("commit %s not found in repository", commitSHA)
|
||||
}
|
||||
|
||||
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, repo, m.RemoteName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get remote URL: %w", err)
|
||||
}
|
||||
|
||||
// Push LFS objects first if enabled (the target commit may reference them)
|
||||
if setting.LFS.StartServer {
|
||||
endpoint := lfs.DetermineEndpoint(remoteURL.String(), "")
|
||||
lfsClient := lfs.NewClient(endpoint, nil)
|
||||
if err := pushAllLFSObjects(ctx, gitRepo, lfsClient); err != nil {
|
||||
return util.SanitizeErrorCredentialURLs(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Push the specific commit to the branch on the remote URL directly
|
||||
// (bypasses the remote's --mirror config which can't be combined with refspecs)
|
||||
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
|
||||
envs := proxy.EnvWithProxy(remoteURL.URL)
|
||||
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
|
||||
Remote: remoteURL.String(),
|
||||
Force: true,
|
||||
Branch: "refs/heads/" + branch,
|
||||
LocalRefName: commitSHA,
|
||||
Timeout: timeout,
|
||||
Env: envs,
|
||||
}); err != nil {
|
||||
return util.SanitizeErrorCredentialURLs(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetAllPushMirrorsForRepo resets the given branch on every push mirror
|
||||
// of a repository to the specified commit SHA. Returns the number of mirrors
|
||||
// successfully reset and any errors encountered (per-mirror).
|
||||
func ResetAllPushMirrorsForRepo(ctx context.Context, repo *repo_model.Repository, branch, commitSHA string) (int, []error) {
|
||||
mirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
return 0, []error{fmt.Errorf("list push mirrors: %w", err)}
|
||||
}
|
||||
if len(mirrors) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
successCount := 0
|
||||
for _, m := range mirrors {
|
||||
if err := ResetPushMirrorBranch(ctx, m, branch, commitSHA); err != nil {
|
||||
errs = append(errs, fmt.Errorf("mirror %s: %w", m.RemoteAddress, err))
|
||||
continue
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
return successCount, errs
|
||||
}
|
||||
|
||||
// AddPushMirrorRemote registers the push mirror remote.
|
||||
func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) error {
|
||||
addRemoteAndConfig := func(storageRepo gitrepo.Repository, addr string) error {
|
||||
@@ -168,6 +330,43 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use filtered push if ExcludeHiddenFiles is enabled and the repo has hidden content
|
||||
if m.ExcludeHiddenFiles {
|
||||
filterOpts := buildFilterOpts(ctx, m.Repo)
|
||||
if filterOpts.HasFilters() {
|
||||
log.Trace("SyncPushMirror [mirror: %d][repo: %-v]: Using filtered push (exclude hidden files)", m.ID, m.Repo)
|
||||
|
||||
// LFS sync still happens normally
|
||||
if setting.LFS.StartServer {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, m.Repo)
|
||||
if err == nil {
|
||||
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, m.Repo, m.RemoteName)
|
||||
if err == nil {
|
||||
endpoint := lfs.DetermineEndpoint(remoteURL.String(), "")
|
||||
lfsClient := lfs.NewClient(endpoint, nil)
|
||||
if err := pushAllLFSObjects(ctx, gitRepo, lfsClient); err != nil {
|
||||
gitRepo.Close()
|
||||
return util.SanitizeErrorCredentialURLs(err)
|
||||
}
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if err := performFilteredPush(ctx, m.Repo, m.RemoteName, filterOpts, timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wiki is always pushed unfiltered (no dot folders concern)
|
||||
if repo_service.HasWiki(ctx, m.Repo) {
|
||||
if _, err := gitrepo.GitRemoteGetURL(ctx, m.Repo.WikiStorageRepo(), m.RemoteName); err == nil {
|
||||
_ = performPush(m.Repo, true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := performPush(m.Repo, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
218
services/org/profile.go
Normal file
218
services/org/profile.go
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2026 MarketAlly. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/db"
|
||||
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
||||
user_model "code.gitcaddy.com/server/v3/models/user"
|
||||
"code.gitcaddy.com/server/v3/modules/git"
|
||||
repo_service "code.gitcaddy.com/server/v3/services/repository"
|
||||
files_service "code.gitcaddy.com/server/v3/services/repository/files"
|
||||
)
|
||||
|
||||
// GetOrgProfileReadme reads the README.md from the org's .profile repository.
|
||||
// Returns empty string if the .profile repo doesn't exist or has no README.
|
||||
func GetOrgProfileReadme(ctx context.Context, orgID int64) (string, error) {
|
||||
profileRepo, err := repo_model.GetRepositoryByName(ctx, orgID, ".profile")
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
if profileRepo.IsEmpty {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, profileRepo.RepoPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit(profileRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Try common README filenames
|
||||
readmeFiles := []string{"README.md", "readme.md", "Readme.md", "README", "README.txt"}
|
||||
for _, filename := range readmeFiles {
|
||||
entry, err := commit.GetTreeEntryByPath(filename)
|
||||
if err == nil && !entry.IsDir() {
|
||||
content, err := entry.Blob().GetBlobContent(1024 * 512) // 512KB max
|
||||
if err == nil {
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// UpdateOrgProfileReadme updates (or creates) the README.md in the org's .profile repository.
|
||||
// If the .profile repo doesn't exist, it is created first.
|
||||
func UpdateOrgProfileReadme(ctx context.Context, doer *user_model.User, orgID int64, content, commitMessage string) error {
|
||||
// Look up the org as a user (needed for repo operations)
|
||||
orgUser, err := user_model.GetUserByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileRepo, err := repo_model.GetRepositoryByName(ctx, orgID, ".profile")
|
||||
if err != nil {
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create .profile repo
|
||||
profileRepo, err = repo_service.CreateRepository(ctx, doer, orgUser, repo_service.CreateRepoOptions{
|
||||
Name: ".profile",
|
||||
Description: "Organization profile",
|
||||
AutoInit: true,
|
||||
Readme: "Default",
|
||||
DefaultBranch: "main",
|
||||
IsPrivate: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if commitMessage == "" {
|
||||
commitMessage = "Update organization profile README"
|
||||
}
|
||||
|
||||
// Determine operation: create or update
|
||||
operation := "update"
|
||||
var existingSHA string
|
||||
|
||||
if !profileRepo.IsEmpty {
|
||||
gitRepo, err := git.OpenRepository(ctx, profileRepo.RepoPath())
|
||||
if err == nil {
|
||||
commit, err := gitRepo.GetBranchCommit(profileRepo.DefaultBranch)
|
||||
if err == nil {
|
||||
entry, err := commit.GetTreeEntryByPath("README.md")
|
||||
if err != nil {
|
||||
operation = "create"
|
||||
} else {
|
||||
existingSHA = entry.ID.String()
|
||||
}
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
} else {
|
||||
operation = "create"
|
||||
}
|
||||
|
||||
_, err = files_service.ChangeRepoFiles(ctx, profileRepo, doer, &files_service.ChangeRepoFilesOptions{
|
||||
OldBranch: profileRepo.DefaultBranch,
|
||||
NewBranch: profileRepo.DefaultBranch,
|
||||
Message: commitMessage,
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: operation,
|
||||
TreePath: "README.md",
|
||||
ContentReader: strings.NewReader(content),
|
||||
SHA: existingSHA,
|
||||
},
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
GitUserName: doer.Name,
|
||||
GitUserEmail: doer.Email,
|
||||
},
|
||||
})
|
||||
|
||||
// If update failed because file doesn't exist, try create
|
||||
if err != nil && operation == "update" {
|
||||
_, err = files_service.ChangeRepoFiles(ctx, profileRepo, doer, &files_service.ChangeRepoFilesOptions{
|
||||
OldBranch: profileRepo.DefaultBranch,
|
||||
NewBranch: profileRepo.DefaultBranch,
|
||||
Message: commitMessage,
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: "README.md",
|
||||
ContentReader: strings.NewReader(content),
|
||||
},
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
GitUserName: doer.Name,
|
||||
GitUserEmail: doer.Email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOrgProfileRepo returns the .profile repository for an org, or nil if it doesn't exist.
|
||||
func GetOrgProfileRepo(ctx context.Context, orgID int64) (*repo_model.Repository, error) {
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, orgID, ".profile")
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// GetOrgRecentActivity returns the N most recently updated repos for an org
|
||||
// with their latest commit info.
|
||||
type RecentRepoActivity struct {
|
||||
RepoName string `json:"repo_name"`
|
||||
RepoFullName string `json:"repo_full_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
CommitMessage string `json:"commit_message,omitempty"`
|
||||
CommitTime int64 `json:"commit_time,omitempty"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
}
|
||||
|
||||
func GetOrgRecentActivity(ctx context.Context, orgID int64, actor *user_model.User, limit int) ([]*RecentRepoActivity, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
showPrivate := actor != nil
|
||||
repos, _, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{PageSize: limit, Page: 1},
|
||||
OwnerID: orgID,
|
||||
OrderBy: db.SearchOrderByRecentUpdated,
|
||||
Private: showPrivate,
|
||||
Actor: actor,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*RecentRepoActivity, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
activity := &RecentRepoActivity{
|
||||
RepoName: repo.Name,
|
||||
RepoFullName: repo.FullName(),
|
||||
DefaultBranch: repo.DefaultBranch,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
|
||||
if err == nil {
|
||||
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
||||
if err == nil {
|
||||
activity.CommitMessage = commit.Summary()
|
||||
activity.CommitTime = commit.Author.When.Unix()
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
result = append(result, activity)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -209,12 +209,55 @@ func TranslateLandingPageContent(ctx context.Context, repo *repo_model.Repositor
|
||||
|
||||
// Validate it's valid JSON
|
||||
result := extractJSON(resp.Result)
|
||||
var check map[string]any
|
||||
if err := json.Unmarshal([]byte(result), &check); err != nil {
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
|
||||
return "", fmt.Errorf("AI returned invalid JSON: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
// Strip placeholder values the AI sometimes returns for empty inputs
|
||||
// (e.g. "<UNKNOWN>", "<EMPTY>", "N/A") so they don't end up saved as translations.
|
||||
stripPlaceholders(parsed)
|
||||
|
||||
cleaned, err := json.Marshal(parsed)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("re-marshal translation: %w", err)
|
||||
}
|
||||
return string(cleaned), nil
|
||||
}
|
||||
|
||||
// stripPlaceholders walks a translation overlay and removes string values that
|
||||
// look like AI placeholders for empty input (e.g. "<UNKNOWN>", "<EMPTY>", "N/A").
|
||||
// This keeps stale "<UNKNOWN>" strings out of the saved translation overlay.
|
||||
func stripPlaceholders(v any) {
|
||||
switch t := v.(type) {
|
||||
case map[string]any:
|
||||
for k, child := range t {
|
||||
if s, ok := child.(string); ok && isPlaceholder(s) {
|
||||
delete(t, k)
|
||||
continue
|
||||
}
|
||||
stripPlaceholders(child)
|
||||
}
|
||||
case []any:
|
||||
for _, item := range t {
|
||||
stripPlaceholders(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isPlaceholder(s string) bool {
|
||||
switch strings.TrimSpace(strings.ToUpper(s)) {
|
||||
case "<UNKNOWN>", "<EMPTY>", "<NULL>", "N/A", "NONE":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setIfNotEmpty assigns v to m[key] only if v is non-empty.
|
||||
func setIfNotEmpty(m map[string]any, key, v string) {
|
||||
if v != "" {
|
||||
m[key] = v
|
||||
}
|
||||
}
|
||||
|
||||
// buildTranslatableContent extracts translatable text from a config for the AI
|
||||
@@ -223,18 +266,26 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
|
||||
// Brand
|
||||
if config.Brand.Name != "" || config.Brand.Tagline != "" {
|
||||
content["brand"] = map[string]any{
|
||||
"name": config.Brand.Name,
|
||||
"tagline": config.Brand.Tagline,
|
||||
brand := map[string]any{}
|
||||
setIfNotEmpty(brand, "name", config.Brand.Name)
|
||||
setIfNotEmpty(brand, "tagline", config.Brand.Tagline)
|
||||
if len(brand) > 0 {
|
||||
content["brand"] = brand
|
||||
}
|
||||
}
|
||||
|
||||
// Hero
|
||||
content["hero"] = map[string]any{
|
||||
"headline": config.Hero.Headline,
|
||||
"subheadline": config.Hero.Subheadline,
|
||||
"primary_cta": map[string]string{"label": config.Hero.PrimaryCTA.Label},
|
||||
"secondary_cta": map[string]string{"label": config.Hero.SecondaryCTA.Label},
|
||||
// Hero — only include non-empty fields so the AI doesn't return "<UNKNOWN>"
|
||||
hero := map[string]any{}
|
||||
setIfNotEmpty(hero, "headline", config.Hero.Headline)
|
||||
setIfNotEmpty(hero, "subheadline", config.Hero.Subheadline)
|
||||
if config.Hero.PrimaryCTA.Label != "" {
|
||||
hero["primary_cta"] = map[string]string{"label": config.Hero.PrimaryCTA.Label}
|
||||
}
|
||||
if config.Hero.SecondaryCTA.Label != "" {
|
||||
hero["secondary_cta"] = map[string]string{"label": config.Hero.SecondaryCTA.Label}
|
||||
}
|
||||
if len(hero) > 0 {
|
||||
content["hero"] = hero
|
||||
}
|
||||
|
||||
// Stats, Value Props, Features (already struct-serializable)
|
||||
@@ -244,47 +295,78 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
if len(config.ValueProps) > 0 {
|
||||
content["value_props"] = config.ValueProps
|
||||
}
|
||||
if config.ValuePropsHeadline != "" {
|
||||
content["value_props_headline"] = config.ValuePropsHeadline
|
||||
}
|
||||
if config.ValuePropsSubheadline != "" {
|
||||
content["value_props_subheadline"] = config.ValuePropsSubheadline
|
||||
}
|
||||
if len(config.Features) > 0 {
|
||||
content["features"] = config.Features
|
||||
}
|
||||
if config.FeaturesHeadline != "" {
|
||||
content["features_headline"] = config.FeaturesHeadline
|
||||
}
|
||||
if config.FeaturesSubheadline != "" {
|
||||
content["features_subheadline"] = config.FeaturesSubheadline
|
||||
}
|
||||
|
||||
// Testimonials
|
||||
if len(config.SocialProof.Testimonials) > 0 {
|
||||
testimonials := make([]map[string]string, 0, len(config.SocialProof.Testimonials))
|
||||
for _, t := range config.SocialProof.Testimonials {
|
||||
testimonials = append(testimonials, map[string]string{
|
||||
"quote": t.Quote,
|
||||
"role": t.Role,
|
||||
})
|
||||
tm := map[string]string{}
|
||||
if t.Quote != "" {
|
||||
tm["quote"] = t.Quote
|
||||
}
|
||||
if t.Role != "" {
|
||||
tm["role"] = t.Role
|
||||
}
|
||||
if len(tm) > 0 {
|
||||
testimonials = append(testimonials, tm)
|
||||
}
|
||||
}
|
||||
if len(testimonials) > 0 {
|
||||
content["social_proof"] = map[string]any{"testimonials": testimonials}
|
||||
}
|
||||
content["social_proof"] = map[string]any{"testimonials": testimonials}
|
||||
}
|
||||
|
||||
// Pricing
|
||||
if config.Pricing.Headline != "" || len(config.Pricing.Plans) > 0 {
|
||||
pricing := map[string]any{
|
||||
"headline": config.Pricing.Headline,
|
||||
"subheadline": config.Pricing.Subheadline,
|
||||
}
|
||||
pricing := map[string]any{}
|
||||
setIfNotEmpty(pricing, "headline", config.Pricing.Headline)
|
||||
setIfNotEmpty(pricing, "subheadline", config.Pricing.Subheadline)
|
||||
if len(config.Pricing.Plans) > 0 {
|
||||
plans := make([]map[string]string, 0, len(config.Pricing.Plans))
|
||||
for _, p := range config.Pricing.Plans {
|
||||
plans = append(plans, map[string]string{
|
||||
"name": p.Name,
|
||||
"period": p.Period,
|
||||
"cta": p.CTA,
|
||||
})
|
||||
plan := map[string]string{}
|
||||
if p.Name != "" {
|
||||
plan["name"] = p.Name
|
||||
}
|
||||
if p.Period != "" {
|
||||
plan["period"] = p.Period
|
||||
}
|
||||
if p.CTA != "" {
|
||||
plan["cta"] = p.CTA
|
||||
}
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
pricing["plans"] = plans
|
||||
}
|
||||
content["pricing"] = pricing
|
||||
if len(pricing) > 0 {
|
||||
content["pricing"] = pricing
|
||||
}
|
||||
}
|
||||
|
||||
// CTA Section
|
||||
content["cta_section"] = map[string]any{
|
||||
"headline": config.CTASection.Headline,
|
||||
"subheadline": config.CTASection.Subheadline,
|
||||
"button": map[string]string{"label": config.CTASection.Button.Label},
|
||||
cta := map[string]any{}
|
||||
setIfNotEmpty(cta, "headline", config.CTASection.Headline)
|
||||
setIfNotEmpty(cta, "subheadline", config.CTASection.Subheadline)
|
||||
if config.CTASection.Button.Label != "" {
|
||||
cta["button"] = map[string]string{"label": config.CTASection.Button.Label}
|
||||
}
|
||||
if len(cta) > 0 {
|
||||
content["cta_section"] = cta
|
||||
}
|
||||
|
||||
// Blog
|
||||
@@ -294,9 +376,9 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
blogHeadline = "Latest Posts"
|
||||
}
|
||||
blog := map[string]any{
|
||||
"headline": blogHeadline,
|
||||
"subheadline": config.Blog.Subheadline,
|
||||
"headline": blogHeadline,
|
||||
}
|
||||
setIfNotEmpty(blog, "subheadline", config.Blog.Subheadline)
|
||||
if config.Blog.CTAButton.Label != "" {
|
||||
blog["cta_button"] = map[string]string{"label": config.Blog.CTAButton.Label}
|
||||
}
|
||||
@@ -309,10 +391,11 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
if galleryHeadline == "" {
|
||||
galleryHeadline = "Gallery"
|
||||
}
|
||||
content["gallery"] = map[string]any{
|
||||
"headline": galleryHeadline,
|
||||
"subheadline": config.Gallery.Subheadline,
|
||||
gallery := map[string]any{
|
||||
"headline": galleryHeadline,
|
||||
}
|
||||
setIfNotEmpty(gallery, "subheadline", config.Gallery.Subheadline)
|
||||
content["gallery"] = gallery
|
||||
}
|
||||
|
||||
// Comparison
|
||||
@@ -321,32 +404,53 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
if compHeadline == "" {
|
||||
compHeadline = "How We Compare"
|
||||
}
|
||||
content["comparison"] = map[string]any{
|
||||
"headline": compHeadline,
|
||||
"subheadline": config.Comparison.Subheadline,
|
||||
comparison := map[string]any{
|
||||
"headline": compHeadline,
|
||||
}
|
||||
setIfNotEmpty(comparison, "subheadline", config.Comparison.Subheadline)
|
||||
content["comparison"] = comparison
|
||||
}
|
||||
|
||||
// Cross-Promote
|
||||
if config.CrossPromote.Enabled {
|
||||
cpHeadline := config.CrossPromote.Headline
|
||||
if cpHeadline == "" {
|
||||
cpHeadline = "Related Offerings"
|
||||
}
|
||||
crossPromote := map[string]any{
|
||||
"headline": cpHeadline,
|
||||
}
|
||||
setIfNotEmpty(crossPromote, "subheadline", config.CrossPromote.Subheadline)
|
||||
content["cross_promote"] = crossPromote
|
||||
}
|
||||
|
||||
// Footer
|
||||
if config.Footer.Copyright != "" || len(config.Footer.Links) > 0 {
|
||||
footer := map[string]any{
|
||||
"copyright": config.Footer.Copyright,
|
||||
}
|
||||
footer := map[string]any{}
|
||||
setIfNotEmpty(footer, "copyright", config.Footer.Copyright)
|
||||
if len(config.Footer.Links) > 0 {
|
||||
links := make([]map[string]string, 0, len(config.Footer.Links))
|
||||
for _, l := range config.Footer.Links {
|
||||
links = append(links, map[string]string{"label": l.Label})
|
||||
if l.Label != "" {
|
||||
links = append(links, map[string]string{"label": l.Label})
|
||||
}
|
||||
}
|
||||
if len(links) > 0 {
|
||||
footer["links"] = links
|
||||
}
|
||||
footer["links"] = links
|
||||
}
|
||||
content["footer"] = footer
|
||||
if len(footer) > 0 {
|
||||
content["footer"] = footer
|
||||
}
|
||||
}
|
||||
|
||||
// SEO
|
||||
if config.SEO.Title != "" || config.SEO.Description != "" {
|
||||
content["seo"] = map[string]any{
|
||||
"title": config.SEO.Title,
|
||||
"description": config.SEO.Description,
|
||||
seo := map[string]any{}
|
||||
setIfNotEmpty(seo, "title", config.SEO.Title)
|
||||
setIfNotEmpty(seo, "description", config.SEO.Description)
|
||||
if len(seo) > 0 {
|
||||
content["seo"] = seo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,16 +465,17 @@ func buildTranslatableContent(config *pages_module.LandingConfig) string {
|
||||
return def
|
||||
}
|
||||
content["navigation"] = map[string]any{
|
||||
"label_value_props": labelOrDefault(config.Navigation.LabelValueProps, defaults.LabelValueProps),
|
||||
"label_features": labelOrDefault(config.Navigation.LabelFeatures, defaults.LabelFeatures),
|
||||
"label_pricing": labelOrDefault(config.Navigation.LabelPricing, defaults.LabelPricing),
|
||||
"label_blog": labelOrDefault(config.Navigation.LabelBlog, defaults.LabelBlog),
|
||||
"label_gallery": labelOrDefault(config.Navigation.LabelGallery, defaults.LabelGallery),
|
||||
"label_compare": labelOrDefault(config.Navigation.LabelCompare, defaults.LabelCompare),
|
||||
"label_docs": labelOrDefault(config.Navigation.LabelDocs, "Docs"),
|
||||
"label_releases": labelOrDefault(config.Navigation.LabelReleases, "Releases"),
|
||||
"label_api": labelOrDefault(config.Navigation.LabelAPI, "API"),
|
||||
"label_issues": labelOrDefault(config.Navigation.LabelIssues, "Issues"),
|
||||
"label_value_props": labelOrDefault(config.Navigation.LabelValueProps, defaults.LabelValueProps),
|
||||
"label_features": labelOrDefault(config.Navigation.LabelFeatures, defaults.LabelFeatures),
|
||||
"label_pricing": labelOrDefault(config.Navigation.LabelPricing, defaults.LabelPricing),
|
||||
"label_blog": labelOrDefault(config.Navigation.LabelBlog, defaults.LabelBlog),
|
||||
"label_gallery": labelOrDefault(config.Navigation.LabelGallery, defaults.LabelGallery),
|
||||
"label_compare": labelOrDefault(config.Navigation.LabelCompare, defaults.LabelCompare),
|
||||
"label_cross_promote": labelOrDefault(config.Navigation.LabelCrossPromote, defaults.LabelCrossPromote),
|
||||
"label_docs": labelOrDefault(config.Navigation.LabelDocs, "Docs"),
|
||||
"label_releases": labelOrDefault(config.Navigation.LabelReleases, "Releases"),
|
||||
"label_api": labelOrDefault(config.Navigation.LabelAPI, "API"),
|
||||
"label_issues": labelOrDefault(config.Navigation.LabelIssues, "Issues"),
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(content)
|
||||
|
||||
@@ -173,6 +173,20 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||
return nil, fmt.Errorf("createDelegateHooks: %w", err)
|
||||
}
|
||||
|
||||
// 5b - Filter hidden files from fork if source repo has HideDotfiles or hidden folders
|
||||
if opts.BaseRepo.HideDotfiles {
|
||||
filterOpts := &git.FilterTreeOptions{ExcludeDotFiles: true}
|
||||
if hiddenPaths, pathErr := repo_model.GetHiddenFolderPaths(ctx, opts.BaseRepo.ID); pathErr == nil && len(hiddenPaths) > 0 {
|
||||
filterOpts.ExcludePaths = hiddenPaths
|
||||
}
|
||||
if filterOpts.HasFilters() {
|
||||
if filterErr := filterForkBranches(ctx, repo, filterOpts); filterErr != nil {
|
||||
log.Error("filterForkBranches failed for %v: %v", repo, filterErr)
|
||||
// Non-fatal — fork still works, just has dot folders
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6 - Sync the repository branches and tags
|
||||
var gitRepo *git.Repository
|
||||
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
||||
@@ -213,6 +227,47 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// filterForkBranches rewrites branch heads to exclude hidden/dot files.
|
||||
// For each branch, it builds a filtered commit (no refs left behind) and
|
||||
// updates the branch ref to point at the filtered commit.
|
||||
func filterForkBranches(ctx context.Context, repo *repo_model.Repository, filterOpts *git.FilterTreeOptions) error {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open repository: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get branches: %w", err)
|
||||
}
|
||||
|
||||
for _, branch := range branches {
|
||||
commitID, err := gitRepo.GetBranchCommitID(branch)
|
||||
if err != nil {
|
||||
log.Warn("filterForkBranches: GetBranchCommitID(%s): %v", branch, err)
|
||||
continue
|
||||
}
|
||||
|
||||
filteredSHA, err := gitRepo.BuildFilteredCommit(commitID, filterOpts)
|
||||
if err != nil {
|
||||
log.Warn("filterForkBranches: BuildFilteredCommit(%s): %v", branch, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if filteredSHA == commitID {
|
||||
continue // nothing filtered
|
||||
}
|
||||
|
||||
// Update the branch ref to point at the filtered commit
|
||||
if err := gitRepo.UpdateRef("refs/heads/"+branch, filteredSHA); err != nil {
|
||||
log.Warn("filterForkBranches: update-ref(%s): %v", branch, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
|
||||
func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
|
||||
@@ -966,7 +966,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.ad-nav { padding: 0 20px; }
|
||||
.ad-nav-links { display: none; }
|
||||
.ad-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.ad-menu-toggle { display: flex; }
|
||||
.ad-hero { padding: 120px 24px 80px; }
|
||||
.ad-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -1016,6 +1028,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ad-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ad-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ad-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="ad-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="ad-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1276,7 +1289,8 @@
|
||||
<div class="ad-features-inner">
|
||||
<div class="ad-section-header ad-reveal">
|
||||
<div class="ad-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Systems Analysis{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ad-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1299,7 +1313,8 @@
|
||||
<div class="ad-features-inner">
|
||||
<div class="ad-section-header ad-reveal">
|
||||
<div class="ad-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Technical Specifications{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ad-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1382,6 +1397,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="ad-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="ad-features-inner">
|
||||
<div class="ad-section-header ad-reveal">
|
||||
<div class="ad-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ad-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="ad-feature-card ad-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="ad-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="ad-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="ad-cta-section" id="cta">
|
||||
|
||||
@@ -1065,7 +1065,19 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nb-nav { padding: 14px 24px; }
|
||||
.nb-nav-links { display: none; }
|
||||
.nb-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.nb-mobile-toggle { display: block; }
|
||||
.nb-mobile-menu { padding: 24px; }
|
||||
.nb-hero { padding: 120px 24px 80px; }
|
||||
@@ -1139,6 +1151,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="nb-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="nb-nav-repo">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1179,6 +1192,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="nb-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="nb-nav-repo">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1404,7 +1418,8 @@
|
||||
<section class="nb-value-props" id="value-props">
|
||||
<div class="nb-section-header nb-reveal">
|
||||
<div class="nb-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose this{{end}}</div>
|
||||
<h2>Unlock your <span class="nb-glow-primary">potential</span></h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}Unlock your <span class="nb-glow-primary">potential</span>{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="nb-value-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1425,7 +1440,8 @@
|
||||
<section class="nb-features" id="features">
|
||||
<div class="nb-section-header nb-reveal">
|
||||
<div class="nb-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>Packed with <span class="nb-glow-text">power</span></h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Packed with <span class="nb-glow-text">power</span>{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="nb-feature-list">
|
||||
{{range .Config.Features}}
|
||||
@@ -1505,6 +1521,32 @@
|
||||
{{end}}
|
||||
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="nb-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="nb-features-inner">
|
||||
<div class="nb-section-header nb-reveal">
|
||||
<div class="nb-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="nb-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="nb-feature-card nb-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="nb-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="nb-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Banner -->
|
||||
<section class="nb-cta-section" id="cta">
|
||||
<div class="nb-cta-banner nb-reveal">
|
||||
|
||||
@@ -970,7 +970,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.ct-nav { padding: 0 20px; }
|
||||
.ct-nav-links { display: none; }
|
||||
.ct-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.ct-menu-toggle { display: flex; }
|
||||
.ct-hero { padding: 120px 20px 80px; }
|
||||
.ct-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -1022,6 +1034,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ct-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ct-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ct-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="ct-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="ct-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="14" height="14" alt="GitCaddy">
|
||||
@@ -1291,7 +1304,8 @@
|
||||
<div class="ct-features-inner">
|
||||
<div class="ct-section-header ct-reveal">
|
||||
<div class="ct-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ct-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1314,7 +1328,8 @@
|
||||
<div class="ct-features-inner">
|
||||
<div class="ct-section-header ct-reveal">
|
||||
<div class="ct-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ct-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1397,6 +1412,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="ct-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="ct-features-inner">
|
||||
<div class="ct-section-header ct-reveal">
|
||||
<div class="ct-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ct-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="ct-feature-card ct-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="ct-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="ct-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="ct-cta-section" id="cta">
|
||||
|
||||
@@ -855,7 +855,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.dt-nav { padding: 0 20px; }
|
||||
.dt-nav-links { display: none; }
|
||||
.dt-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.dt-menu-toggle { display: flex; }
|
||||
.dt-hero { padding: 100px 24px 80px; }
|
||||
.dt-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -905,6 +917,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="dt-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="dt-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="dt-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="dt-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="dt-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1164,7 +1177,8 @@
|
||||
<div class="dt-features-inner">
|
||||
<div class="dt-section-header dt-reveal">
|
||||
<div class="dt-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="dt-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1187,7 +1201,8 @@
|
||||
<div class="dt-features-inner">
|
||||
<div class="dt-section-header dt-reveal">
|
||||
<div class="dt-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="dt-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1270,6 +1285,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="dt-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="dt-features-inner">
|
||||
<div class="dt-section-header dt-reveal">
|
||||
<div class="dt-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="dt-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="dt-feature-card dt-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="dt-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="dt-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="dt-cta-section" id="cta">
|
||||
|
||||
@@ -880,7 +880,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.df-nav { padding: 0 20px; }
|
||||
.df-nav-links { display: none; }
|
||||
.df-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.df-menu-toggle { display: flex; }
|
||||
.df-hero { padding: 120px 24px 80px; }
|
||||
.df-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -930,6 +942,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="df-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="df-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="df-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="df-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="df-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1188,7 +1201,8 @@
|
||||
<div class="df-features-inner">
|
||||
<div class="df-section-header df-reveal">
|
||||
<div class="df-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="df-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1211,7 +1225,8 @@
|
||||
<div class="df-features-inner">
|
||||
<div class="df-section-header df-reveal">
|
||||
<div class="df-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="df-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1294,6 +1309,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="df-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="df-features-inner">
|
||||
<div class="df-section-header df-reveal">
|
||||
<div class="df-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="df-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="df-feature-card df-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="df-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="df-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="df-cta-section" id="cta">
|
||||
|
||||
@@ -1005,6 +1005,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="ea-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="ea-btn-text">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1040,6 +1041,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="ea-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="ea-btn-text">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1268,7 +1270,8 @@
|
||||
{{if .Config.ValueProps}}
|
||||
<section class="ea-section" id="why">
|
||||
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose this{{end}}</div>
|
||||
<h2 class="ea-section-title ea-reveal">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2 class="ea-section-title ea-reveal">{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
{{range $i, $v := .Config.ValueProps}}
|
||||
<div class="ea-value-item ea-reveal">
|
||||
<div class="ea-value-number">{{$i | printf "%d"}}</div>
|
||||
@@ -1285,7 +1288,8 @@
|
||||
{{if .Config.Features}}
|
||||
<section class="ea-section" id="features">
|
||||
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2 class="ea-section-title ea-reveal">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2 class="ea-section-title ea-reveal">{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
<div class="ea-accordion ea-reveal">
|
||||
{{range $i, $f := .Config.Features}}
|
||||
<div class="ea-accordion-item" data-index="{{$i}}">
|
||||
@@ -1372,6 +1376,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="ea-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="ea-features-inner">
|
||||
<div class="ea-section-header ea-reveal">
|
||||
<div class="ea-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="ea-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="ea-feature-card ea-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="ea-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="ea-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="ea-cta-section" id="cta">
|
||||
|
||||
@@ -926,7 +926,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.osh-nav { padding: 0 20px; }
|
||||
.osh-nav-links { display: none; }
|
||||
.osh-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.osh-menu-toggle { display: flex; }
|
||||
.osh-hero { padding: 120px 24px 80px; }
|
||||
.osh-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -996,6 +1008,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="osh-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="osh-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="osh-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="osh-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="osh-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1256,7 +1269,8 @@
|
||||
<div class="osh-features-inner">
|
||||
<div class="osh-section-header osh-reveal">
|
||||
<div class="osh-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="osh-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1279,7 +1293,8 @@
|
||||
<div class="osh-features-inner">
|
||||
<div class="osh-section-header osh-reveal">
|
||||
<div class="osh-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="osh-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1362,6 +1377,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="osh-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="osh-features-inner">
|
||||
<div class="osh-section-header osh-reveal">
|
||||
<div class="osh-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="osh-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="osh-feature-card osh-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="osh-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="osh-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="osh-cta-section" id="cta">
|
||||
|
||||
@@ -1043,7 +1043,19 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gm-nav { top: 8px; left: 8px; right: 8px; padding: 12px 20px; }
|
||||
.gm-nav-links { display: none; }
|
||||
.gm-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.gm-mobile-toggle { display: block; }
|
||||
.gm-hero { padding: 120px 24px 80px; }
|
||||
.gm-hero h1 { font-size: 36px; }
|
||||
@@ -1114,6 +1126,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="gm-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="gm-nav-repo">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1154,6 +1167,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="gm-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="gm-nav-repo">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1409,8 +1423,8 @@
|
||||
<section class="gm-section" id="value-props">
|
||||
<div class="gm-section-inner">
|
||||
<div class="gm-section-header gm-reveal">
|
||||
<h2>Why <span class="gm-serif">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span></h2>
|
||||
<p>Built for developers who value their time</p>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}Why <span class="gm-serif">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>{{end}}</h2>
|
||||
<p>{{if .Config.ValuePropsSubheadline}}{{.Config.ValuePropsSubheadline}}{{else}}Built for developers who value their time{{end}}</p>
|
||||
</div>
|
||||
|
||||
<div class="gm-value-grid">
|
||||
@@ -1430,8 +1444,8 @@
|
||||
<section id="features" class="gm-section">
|
||||
<div class="gm-section-inner">
|
||||
<div class="gm-section-header gm-reveal">
|
||||
<h2>How it <span class="gm-serif">works</span></h2>
|
||||
<p>Get started in minutes, not weeks</p>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}How it <span class="gm-serif">works</span>{{end}}</h2>
|
||||
<p>{{if .Config.FeaturesSubheadline}}{{.Config.FeaturesSubheadline}}{{else}}Get started in minutes, not weeks{{end}}</p>
|
||||
</div>
|
||||
|
||||
<div class="gm-features-list">
|
||||
@@ -1651,6 +1665,32 @@
|
||||
|
||||
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="gm-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="gm-features-inner">
|
||||
<div class="gm-section-header gm-reveal">
|
||||
<div class="gm-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="gm-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="gm-feature-card gm-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="gm-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="gm-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="gm-footer">
|
||||
<div class="gm-footer-brand">
|
||||
|
||||
@@ -979,7 +979,19 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.vs-nav { padding: 0 20px; }
|
||||
.vs-nav-links { display: none; }
|
||||
.vs-nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px;
|
||||
gap: 8px;
|
||||
background: inherit;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
.vs-menu-toggle { display: flex; }
|
||||
.vs-hero { padding: 120px 24px 80px; }
|
||||
.vs-hero-ctas { flex-direction: column; align-items: center; }
|
||||
@@ -1032,6 +1044,7 @@
|
||||
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="vs-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
|
||||
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="vs-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
|
||||
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="vs-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}<a href="{{.LandingURL}}#cross-promote" class="vs-nav-link">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</a>{{end}}
|
||||
{{if .Config.Navigation.ShowRepository}}
|
||||
<a href="{{.RepoURL}}" class="vs-nav-cta">
|
||||
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
|
||||
@@ -1296,7 +1309,8 @@
|
||||
<div class="vs-features-inner">
|
||||
<div class="vs-section-header vs-reveal">
|
||||
<div class="vs-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
<h2>{{if .Config.ValuePropsHeadline}}{{.Config.ValuePropsHeadline}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
|
||||
{{if .Config.ValuePropsSubheadline}}<p>{{.Config.ValuePropsSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="vs-features-grid">
|
||||
{{range .Config.ValueProps}}
|
||||
@@ -1319,7 +1333,8 @@
|
||||
<div class="vs-features-inner">
|
||||
<div class="vs-section-header vs-reveal">
|
||||
<div class="vs-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
|
||||
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
|
||||
<h2>{{if .Config.FeaturesHeadline}}{{.Config.FeaturesHeadline}}{{else}}Features{{end}}</h2>
|
||||
{{if .Config.FeaturesSubheadline}}<p>{{.Config.FeaturesSubheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="vs-features-grid">
|
||||
{{range .Config.Features}}
|
||||
@@ -1402,6 +1417,32 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Cross-Promote Section -->
|
||||
{{if and .Config.CrossPromote.Enabled .CrossPromoteItems}}
|
||||
<section class="vs-features" id="cross-promote" style="border-top: 1px solid rgba(255,255,255,0.04);">
|
||||
<div class="vs-features-inner">
|
||||
<div class="vs-section-header vs-reveal">
|
||||
<div class="vs-section-label">{{if .Config.Navigation.LabelCrossPromote}}{{.Config.Navigation.LabelCrossPromote}}{{else}}Related Offerings{{end}}</div>
|
||||
<h2>{{if .Config.CrossPromote.Headline}}{{.Config.CrossPromote.Headline}}{{else}}Related Offerings{{end}}</h2>
|
||||
{{if .Config.CrossPromote.Subheadline}}<p>{{.Config.CrossPromote.Subheadline}}</p>{{end}}
|
||||
</div>
|
||||
<div class="vs-features-grid">
|
||||
{{range .CrossPromoteItems}}
|
||||
<a href="{{.URL}}" class="vs-feature-card vs-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; text-align: center;">
|
||||
{{if .LogoURL}}
|
||||
<div style="margin-bottom: 16px;">
|
||||
<img src="{{.LogoURL}}" alt="{{.Name}}" style="max-width: 120px; max-height: 60px; object-fit: contain;">
|
||||
</div>
|
||||
{{end}}
|
||||
<h3 class="vs-feature-title">{{.Name}}</h3>
|
||||
{{if .Description}}<p class="vs-feature-desc">{{.Description}}</p>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- CTA Section -->
|
||||
{{if .Config.CTASection.Headline}}
|
||||
<section class="vs-cta-section" id="cta">
|
||||
|
||||
@@ -44,6 +44,39 @@
|
||||
|
||||
<div class="help tw-mb-4">{{ctx.Locale.Tr "repo.settings.license_help"}}</div>
|
||||
|
||||
<!-- License-specific fields -->
|
||||
<div class="license-fields" data-license="ACL-1.0" style="display: none;">
|
||||
<div class="ui dividing header tw-mt-4">{{ctx.Locale.Tr "repo.settings.license_acl_fields"}}</div>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.license_acl_fields_desc"}}</p>
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_acl_jurisdiction"}}</label>
|
||||
<input name="acl_jurisdiction" placeholder="the State of Delaware, United States">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.license_acl_jurisdiction_help"}}</p>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_acl_venue"}}</label>
|
||||
<input name="acl_venue" placeholder="Wilmington, Delaware">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.license_acl_venue_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_acl_contact"}}</label>
|
||||
<input name="acl_contact" placeholder="legal@example.com">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.license_acl_contact_help"}}</p>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_acl_license_url"}}</label>
|
||||
<input name="acl_license_url" placeholder="https://example.com/LICENSE.md">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.license_acl_tier_url"}}</label>
|
||||
<input name="acl_tier_url" placeholder="https://example.com/tiers">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<button class="ui primary button" type="submit" name="action" value="link">
|
||||
{{svg "octicon-link" 16}} {{ctx.Locale.Tr "repo.settings.license_link"}}
|
||||
@@ -116,6 +149,16 @@
|
||||
|
||||
let scannedLicense = '';
|
||||
|
||||
// Show/hide license-specific fields based on selected license
|
||||
function updateLicenseFields() {
|
||||
const selected = licenseSelect.value;
|
||||
document.querySelectorAll('.license-fields').forEach(el => {
|
||||
el.style.display = (el.dataset.license === selected) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
licenseSelect.addEventListener('change', updateLicenseFields);
|
||||
updateLicenseFields();
|
||||
|
||||
// Scan button handler
|
||||
scanBtn?.addEventListener('click', async function() {
|
||||
const btn = this;
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<details class="item toggleable-item" {{if or .PageIsSettingsPagesGeneral .PageIsSettingsPagesBrand .PageIsSettingsPagesHero .PageIsSettingsPagesContent .PageIsSettingsPagesComparison .PageIsSettingsPagesSocial .PageIsSettingsPagesPricing .PageIsSettingsPagesFooter .PageIsSettingsPagesTheme .PageIsSettingsPagesLanguages}}open{{end}}>
|
||||
<details class="item toggleable-item" {{if or .PageIsSettingsPagesGeneral .PageIsSettingsPagesBrand .PageIsSettingsPagesHero .PageIsSettingsPagesContent .PageIsSettingsPagesComparison .PageIsSettingsPagesSocial .PageIsSettingsPagesPricing .PageIsSettingsPagesFooter .PageIsSettingsPagesTheme .PageIsSettingsPagesLanguages .PageIsSettingsPagesAdvanced}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "repo.settings.pages"}}</summary>
|
||||
<div class="menu">
|
||||
<a class="{{if .PageIsSettingsPagesGeneral}}active {{end}}item" href="{{.RepoLink}}/settings/pages">
|
||||
@@ -86,6 +86,9 @@
|
||||
<a class="{{if .PageIsSettingsPagesLanguages}}active {{end}}item" href="{{.RepoLink}}/settings/pages/languages">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.languages"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesAdvanced}}active {{end}}item" href="{{.RepoLink}}/settings/pages/advanced">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.advanced"}}
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables .PageIsActionsSettingsGeneral}}open{{end}}>
|
||||
|
||||
@@ -226,6 +226,15 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="tw-text-right">
|
||||
<button
|
||||
class="ui tiny button show-modal"
|
||||
data-modal="#push-mirror-reset-modal"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_tooltip"}}"
|
||||
data-modal-push-mirror-reset-id="{{.ID}}"
|
||||
data-modal-push-mirror-reset-address="{{.RemoteAddress}}"
|
||||
>
|
||||
{{svg "octicon-history" 14}}
|
||||
</button>
|
||||
<button
|
||||
class="ui tiny button show-modal"
|
||||
data-modal="#push-mirror-edit-modal"
|
||||
@@ -233,6 +242,7 @@
|
||||
data-modal-push-mirror-edit-id="{{.ID}}"
|
||||
data-modal-push-mirror-edit-interval="{{.Interval}}"
|
||||
data-modal-push-mirror-edit-address="{{.RemoteAddress}}"
|
||||
data-modal-push-mirror-edit-exclude-hidden.checked="{{.ExcludeHiddenFiles}}"
|
||||
>
|
||||
{{svg "octicon-pencil" 14}}
|
||||
</button>
|
||||
@@ -285,6 +295,15 @@
|
||||
<label for="push_mirror_sync_on_commit">{{ctx.Locale.Tr "repo.mirror_sync_on_commit"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
{{if .Repository.HideDotfiles}}
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input id="push_mirror_exclude_hidden_files" name="push_mirror_exclude_hidden_files" type="checkbox" checked>
|
||||
<label for="push_mirror_exclude_hidden_files">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.exclude_hidden_files"}}</label>
|
||||
</div>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.exclude_hidden_files_desc"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="inline field {{if .Err_PushMirrorInterval}}error{{end}}">
|
||||
<label for="push_mirror_interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
|
||||
<input id="push_mirror_interval" name="push_mirror_interval" value="{{if .push_mirror_interval}}{{.push_mirror_interval}}{{else}}{{.DefaultMirrorInterval}}{{end}}">
|
||||
@@ -298,6 +317,16 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{if .PushMirrors}}
|
||||
<div class="tw-mt-3">
|
||||
<button class="ui basic red button show-modal"
|
||||
data-modal="#push-mirror-reset-all-modal"
|
||||
type="button">
|
||||
{{svg "octicon-history" 14}} {{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_button"}}
|
||||
</button>
|
||||
<p class="help tw-mt-1">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_button_help"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -880,6 +909,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Repository.IsEmpty}}
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">{{ctx.Locale.Tr "repo.settings.repo_reset.title"}}</div>
|
||||
<div class="flex-item-body">{{ctx.Locale.Tr "repo.settings.repo_reset.desc"}}</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<button class="ui basic red show-modal button" data-modal="#repo-reset-modal">{{ctx.Locale.Tr "repo.settings.repo_reset.button"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">{{ctx.Locale.Tr "repo.settings.delete"}}</div>
|
||||
@@ -1014,6 +1054,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui small modal" id="repo-reset-modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.settings.repo_reset.title"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui error message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.settings.repo_reset.warning_title"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.settings.repo_reset.warning_body"}}</p>
|
||||
<ul>
|
||||
<li>{{ctx.Locale.Tr "repo.settings.repo_reset.warning_branch"}}</li>
|
||||
<li>{{ctx.Locale.Tr "repo.settings.repo_reset.warning_collaborators"}}</li>
|
||||
<li>{{ctx.Locale.Tr "repo.settings.repo_reset.warning_mirrors"}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="repo-reset">
|
||||
<div class="required field">
|
||||
<label for="repo_reset_branch">{{ctx.Locale.Tr "repo.settings.repo_reset.branch"}}</label>
|
||||
<input id="repo_reset_branch" name="repo_reset_branch" value="{{.Repository.DefaultBranch}}" required>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="repo_reset_commit">{{ctx.Locale.Tr "repo.settings.repo_reset.commit"}}</label>
|
||||
<input id="repo_reset_commit" name="repo_reset_commit" placeholder="abc123def456..." required>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="repo_reset_confirm_name">{{ctx.Locale.Tr "repo.settings.repo_reset.confirm_name" .Repository.Name}}</label>
|
||||
<input id="repo_reset_confirm_name" name="repo_name" required>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui cancel button" type="button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button" type="submit">{{ctx.Locale.Tr "repo.settings.repo_reset.confirm"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui small modal" id="delete-repo-modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.settings.delete"}}
|
||||
@@ -1152,3 +1229,5 @@
|
||||
{{end}}
|
||||
|
||||
{{template "repo/settings/push_mirror_sync_modal" .}}
|
||||
{{template "repo/settings/push_mirror_reset_modal" .}}
|
||||
{{template "repo/settings/push_mirror_reset_all_modal" .}}
|
||||
|
||||
@@ -57,33 +57,6 @@
|
||||
<textarea name="custom_head" rows="4" placeholder="<meta ...>">{{.Config.Advanced.CustomHead}}</textarea>
|
||||
</div>
|
||||
|
||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.app_stores"}}</h5>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Google Play ID</label>
|
||||
<input name="google_play_id" value="{{.Config.Advanced.GooglePlayID}}" placeholder="com.example.app">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>App Store ID</label>
|
||||
<input name="app_store_id" value="{{.Config.Advanced.AppStoreID}}" placeholder="123456789">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline fields">
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="public_releases" {{if .Config.Advanced.PublicReleases}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.public_releases"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="hide_mobile_releases" {{if .Config.Advanced.HideMobileReleases}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.hide_mobile_releases"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
</div>
|
||||
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.brand_or"}}</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_logo_url"}}</label>
|
||||
<input name="brand_logo_url" value="{{.Config.Brand.LogoURL}}" placeholder="https://example.com/logo.svg">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.brand_logo_url_help"}}</p>
|
||||
</div>
|
||||
@@ -68,6 +69,7 @@
|
||||
</div>
|
||||
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.brand_or"}}</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_favicon_url"}}</label>
|
||||
<input name="brand_favicon_url" value="{{.Config.Brand.FaviconURL}}" placeholder="https://example.com/favicon.ico">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.brand_favicon_url_help"}}</p>
|
||||
</div>
|
||||
|
||||
@@ -124,6 +124,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.cross_promote_section"}}</h5>
|
||||
<div class="inline field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" name="cross_promote_enabled" {{if .Config.CrossPromote.Enabled}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.cross_promote_enabled_desc"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.cross_promote_headline"}}</label>
|
||||
<input name="cross_promote_headline" value="{{.Config.CrossPromote.Headline}}" placeholder="Related Offerings">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.cross_promote_subheadline"}}</label>
|
||||
<input name="cross_promote_subheadline" value="{{.Config.CrossPromote.Subheadline}}" placeholder="Explore our other projects">
|
||||
</div>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.cross_promote_help"}}</p>
|
||||
|
||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.stats"}}</h5>
|
||||
<div id="stats-container">
|
||||
{{range $i, $stat := .Config.Stats}}
|
||||
@@ -142,6 +159,22 @@
|
||||
<button type="button" class="ui mini button" onclick="addStat()">+ Add Stat</button>
|
||||
|
||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.value_props"}}</h5>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.label_value_props"}}</label>
|
||||
<input name="label_value_props" value="{{.Config.Navigation.LabelValueProps}}" placeholder="Why choose us">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.label_value_props_help"}}</p>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.value_props_headline"}}</label>
|
||||
<input name="value_props_headline" value="{{.Config.ValuePropsHeadline}}" placeholder="Built for makers">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.value_props_headline_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.value_props_subheadline"}}</label>
|
||||
<input name="value_props_subheadline" value="{{.Config.ValuePropsSubheadline}}" placeholder="A short tagline beneath the headline">
|
||||
</div>
|
||||
</div>
|
||||
<div id="valueprops-container">
|
||||
{{range $i, $vp := .Config.ValueProps}}
|
||||
<div class="fields valueprop-item">
|
||||
@@ -209,6 +242,22 @@
|
||||
<button type="button" class="ui mini button" onclick="addValueProp()">+ Add Value Prop</button>
|
||||
|
||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.features"}}</h5>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.label_features"}}</label>
|
||||
<input name="label_features" value="{{.Config.Navigation.LabelFeatures}}" placeholder="Capabilities">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.label_features_help"}}</p>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.features_headline"}}</label>
|
||||
<input name="features_headline" value="{{.Config.FeaturesHeadline}}" placeholder="Everything you need">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.features_headline_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.features_subheadline"}}</label>
|
||||
<input name="features_subheadline" value="{{.Config.FeaturesSubheadline}}" placeholder="A short tagline beneath the headline">
|
||||
</div>
|
||||
</div>
|
||||
<div id="features-container">
|
||||
{{range $i, $f := .Config.Features}}
|
||||
<div class="fields feature-item">
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
</div>
|
||||
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.hero_or"}}</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pages.image_url"}}</label>
|
||||
<input name="image_url" value="{{.Config.Hero.ImageURL}}" placeholder="https://example.com/hero.png">
|
||||
</div>
|
||||
<br>
|
||||
{{end}}
|
||||
|
||||
<div class="field">
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
{{if .PagesEnabled}}
|
||||
<div class="ui secondary pointing menu tw-mb-4">
|
||||
<a class="{{if .PageIsSettingsPagesGeneral}}active {{end}}item" href="{{.RepoLink}}/settings/pages">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.general"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesBrand}}active {{end}}item" href="{{.RepoLink}}/settings/pages/brand">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.brand"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesHero}}active {{end}}item" href="{{.RepoLink}}/settings/pages/hero">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.hero"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesContent}}active {{end}}item" href="{{.RepoLink}}/settings/pages/content">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.content"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesComparison}}active {{end}}item" href="{{.RepoLink}}/settings/pages/comparison">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.comparison"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesSocial}}active {{end}}item" href="{{.RepoLink}}/settings/pages/social">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.social"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesPricing}}active {{end}}item" href="{{.RepoLink}}/settings/pages/pricing">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.pricing"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesFooter}}active {{end}}item" href="{{.RepoLink}}/settings/pages/footer">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.footer"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesTheme}}active {{end}}item" href="{{.RepoLink}}/settings/pages/theme">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.theme"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesLanguages}}active {{end}}item" href="{{.RepoLink}}/settings/pages/languages">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.languages"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsPagesAdvanced}}active {{end}}item" href="{{.RepoLink}}/settings/pages/advanced">
|
||||
{{ctx.Locale.Tr "repo.settings.pages.advanced"}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
33
templates/repo/settings/push_mirror_reset_all_modal.tmpl
Normal file
33
templates/repo/settings/push_mirror_reset_all_modal.tmpl
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="ui small modal" id="push-mirror-reset-all-modal">
|
||||
<div class="header">
|
||||
{{svg "octicon-history"}} {{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_title"}}
|
||||
</div>
|
||||
<form class="content ui form ignore-dirty" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="push-mirror-reset-all">
|
||||
<div class="ui warning message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_warning_title"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_warning_body"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="push-mirror-reset-all-branch">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_branch"}}</label>
|
||||
<input id="push-mirror-reset-all-branch" name="push_mirror_reset_branch" value="{{.Repository.DefaultBranch}}" required>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_branch_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="push-mirror-reset-all-commit">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_commit"}}</label>
|
||||
<input id="push-mirror-reset-all-commit" name="push_mirror_reset_commit" placeholder="abc123def456..." required>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_commit_help"}}</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui small basic cancel button" type="button">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "cancel"}}
|
||||
</button>
|
||||
<button class="ui red small approve button" type="submit">
|
||||
{{svg "octicon-history"}}
|
||||
{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_all_confirm"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
38
templates/repo/settings/push_mirror_reset_modal.tmpl
Normal file
38
templates/repo/settings/push_mirror_reset_modal.tmpl
Normal file
@@ -0,0 +1,38 @@
|
||||
<div class="ui small modal" id="push-mirror-reset-modal">
|
||||
<div class="header">
|
||||
{{svg "octicon-history"}} {{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_title"}}
|
||||
</div>
|
||||
<form class="content ui form ignore-dirty" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="push-mirror-reset">
|
||||
<input type="hidden" name="push_mirror_id" id="push-mirror-reset-id">
|
||||
<div class="ui warning message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_warning_title"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_warning_body"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.mirror_settings.mirrored_repository"}}</label>
|
||||
<span id="push-mirror-reset-address"></span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="push-mirror-reset-branch">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_branch"}}</label>
|
||||
<input id="push-mirror-reset-branch" name="push_mirror_reset_branch" value="{{.Repository.DefaultBranch}}" required>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_branch_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="push-mirror-reset-commit">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_commit"}}</label>
|
||||
<input id="push-mirror-reset-commit" name="push_mirror_reset_commit" placeholder="abc123def456..." required>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_commit_help"}}</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui small basic cancel button" type="button">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "cancel"}}
|
||||
</button>
|
||||
<button class="ui red small approve button" type="submit">
|
||||
{{svg "octicon-history"}}
|
||||
{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.reset_confirm"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -13,6 +13,15 @@
|
||||
<label for="push-mirror-edit-interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
|
||||
<input id="push-mirror-edit-interval" name="push_mirror_interval" class="tw-w-auto">
|
||||
</div>
|
||||
{{if .Repository.HideDotfiles}}
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input id="push-mirror-edit-exclude-hidden" name="push_mirror_exclude_hidden_files" type="checkbox">
|
||||
<label for="push-mirror-edit-exclude-hidden">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.exclude_hidden_files"}}</label>
|
||||
</div>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.exclude_hidden_files_desc"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="actions">
|
||||
<button class="ui small basic cancel button">
|
||||
{{svg "octicon-x"}}
|
||||
|
||||
Reference in New Issue
Block a user