mirror of
https://github.com/go-gitea/gitea.git
synced 2024-11-26 02:10:04 +08:00
77e29e0c39
Add new option: `visible`: witch can hide a specific field of the form or the created content afterwards It is a string array witch can contain `form` and `content`. If only `form` is present, it wont show up in the created issue afterwards and the other way around. By default it sets both except for markdown As they are optional and github don't have any similar thing, it is non breaking and also do not conflict with it. With this you can: - define "post issue creation" elements like a TODO list to track an issue state - make sure to have a checkbox that reminds the user to check for a thing but dont have it in the created issue afterwards - define markdown for the created issue (was the downside of using yaml instead of md in the past) - ... ## Demo ```yaml name: New Contribution description: External Contributor creating a pull body: - type: checkboxes id: extern-todo visible: [form] attributes: label: Contribution Guidelines options: - label: I checked there exist no similar feature to be extended required: true - label: I did read the CONTRIBUTION.MD required: true - type: checkboxes id: intern-todo visible: [content] attributes: label: Maintainer Check-List options: - label: Does this pull follow the KISS principe - label: Checked if internal bord was notifyed # .... ``` [Demo Video](https://cloud.obermui.de/s/tm34fSAbJp9qw9z/download/vid-20240220-152751.mkv) --- *Sponsored by Kithara Software GmbH* --------- Co-authored-by: John Olheiser <john.olheiser@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package template
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"strconv"
|
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/markup/markdown"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/util"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// CouldBe indicates a file with the filename could be a template,
|
|
// it is a low cost check before further processing.
|
|
func CouldBe(filename string) bool {
|
|
it := &api.IssueTemplate{
|
|
FileName: filename,
|
|
}
|
|
return it.Type() != ""
|
|
}
|
|
|
|
// Unmarshal parses out a valid template from the content
|
|
func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
|
|
it, err := unmarshal(filename, content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := Validate(it); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return it, nil
|
|
}
|
|
|
|
// UnmarshalFromEntry parses out a valid template from the blob in entry
|
|
func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
|
|
return unmarshalFromEntry(entry, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here
|
|
}
|
|
|
|
// UnmarshalFromCommit parses out a valid template from the commit
|
|
func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) {
|
|
entry, err := commit.GetTreeEntryByPath(filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get entry for %q: %w", filename, err)
|
|
}
|
|
return unmarshalFromEntry(entry, filename)
|
|
}
|
|
|
|
// UnmarshalFromRepo parses out a valid template from the head commit of the branch
|
|
func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) {
|
|
commit, err := repo.GetBranchCommit(branch)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get commit on branch %q: %w", branch, err)
|
|
}
|
|
|
|
return UnmarshalFromCommit(commit, filename)
|
|
}
|
|
|
|
func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) {
|
|
if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize {
|
|
return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size)
|
|
}
|
|
|
|
r, err := entry.Blob().DataAsync()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("data async: %w", err)
|
|
}
|
|
defer r.Close()
|
|
|
|
content, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read all: %w", err)
|
|
}
|
|
|
|
return Unmarshal(filename, content)
|
|
}
|
|
|
|
func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
|
|
it := &api.IssueTemplate{
|
|
FileName: filename,
|
|
}
|
|
|
|
// Compatible with treating description as about
|
|
compatibleTemplate := &struct {
|
|
About string `yaml:"description"`
|
|
}{}
|
|
|
|
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
|
|
if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil {
|
|
// The only thing we know here is that we can't extract metadata from the content,
|
|
// it's hard to tell if metadata doesn't exist or metadata isn't valid.
|
|
// There's an example template:
|
|
//
|
|
// ---
|
|
// # Title
|
|
// ---
|
|
// Content
|
|
//
|
|
// It could be a valid markdown with two horizontal lines, or an invalid markdown with wrong metadata.
|
|
|
|
it.Content = string(content)
|
|
it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
|
|
it.About, _ = util.SplitStringAtByteN(it.Content, 80)
|
|
} else {
|
|
it.Content = templateBody
|
|
if it.About == "" {
|
|
if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
|
|
it.About = compatibleTemplate.About
|
|
}
|
|
}
|
|
}
|
|
} else if typ == api.IssueTemplateTypeYaml {
|
|
if err := yaml.Unmarshal(content, it); err != nil {
|
|
return nil, fmt.Errorf("yaml unmarshal: %w", err)
|
|
}
|
|
if it.About == "" {
|
|
if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" {
|
|
it.About = compatibleTemplate.About
|
|
}
|
|
}
|
|
for i, v := range it.Fields {
|
|
// set default id value
|
|
if v.ID == "" {
|
|
v.ID = strconv.Itoa(i)
|
|
}
|
|
// set default visibility
|
|
if v.Visible == nil {
|
|
v.Visible = []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm}
|
|
// markdown is not submitted by default
|
|
if v.Type != api.IssueFormFieldTypeMarkdown {
|
|
v.Visible = append(v.Visible, api.IssueFormFieldVisibleContent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return it, nil
|
|
}
|