davrot Mods
This commit is contained in:
parent
f3862b1c33
commit
25bd68b747
15 changed files with 234 additions and 72 deletions
|
@ -6,6 +6,7 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -102,6 +103,30 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
|
|||
return filelist, err
|
||||
}
|
||||
|
||||
// Gives a list of all files in a directory and below
|
||||
func (repo *Repository) LsFilesFromDirectory(directory, branch string) ([]string, error) {
|
||||
if branch == "" {
|
||||
return nil, errors.New("branch not found in context URL")
|
||||
}
|
||||
|
||||
cmd := NewCommand(repo.Ctx, "ls-files").AddDynamicArguments("--with-tree="+branch)
|
||||
if len(directory) > 0 {
|
||||
cmd = NewCommand(repo.Ctx, "ls-files").AddDynamicArguments("--with-tree="+branch).AddDynamicArguments("--directory").AddDynamicArguments(directory)
|
||||
}
|
||||
res, stderror, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(stderror) > 0 {
|
||||
return nil, errors.New(string(stderror))
|
||||
}
|
||||
|
||||
lines := strings.Split(string(res), "\n")
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
// RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present.
|
||||
func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
|
|
|
@ -85,6 +85,8 @@ var Service = struct {
|
|||
DefaultOrgMemberVisible bool
|
||||
UserDeleteWithCommentsMaxTime time.Duration
|
||||
ValidSiteURLSchemes []string
|
||||
LandingPageInfoEnabled bool
|
||||
SignInForgottenPasswordEnabled bool
|
||||
|
||||
// OpenID settings
|
||||
EnableOpenIDSignIn bool
|
||||
|
@ -213,6 +215,8 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
|||
if Service.EnableTimetracking {
|
||||
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
|
||||
}
|
||||
Service.LandingPageInfoEnabled = sec.Key("ENABLE_LANDING_PAGE_INFO").MustBool(true)
|
||||
Service.SignInForgottenPasswordEnabled = sec.Key("SIGNIN_FORGOTTEN_PASSWORD_ENABLED").MustBool(true)
|
||||
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
|
||||
Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
|
||||
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
|
||||
|
|
|
@ -169,6 +169,8 @@ func SignIn(ctx *context.Context) {
|
|||
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
||||
context.SetCaptchaData(ctx)
|
||||
}
|
||||
|
||||
ctx.Data["SignInForgottenPasswordEnabled"] = setting.Service.SignInForgottenPasswordEnabled
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSignIn)
|
||||
}
|
||||
|
@ -189,6 +191,7 @@ func SignInPost(ctx *context.Context) {
|
|||
ctx.Data["PageIsLogin"] = true
|
||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
||||
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
|
||||
ctx.Data["SignInForgottenPasswordEnabled"] = setting.Service.SignInForgottenPasswordEnabled
|
||||
|
||||
// Permission denied if EnableInternalSignIn is false
|
||||
if !setting.Service.EnableInternalSignIn {
|
||||
|
|
|
@ -59,6 +59,8 @@ func Home(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.Data["LandingPageInfoEnabled"] = setting.Service.LandingPageInfoEnabled
|
||||
|
||||
ctx.Data["PageIsHome"] = true
|
||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.HTML(http.StatusOK, tplHome)
|
||||
|
|
|
@ -489,8 +489,23 @@ func DeleteFile(ctx *context.Context) {
|
|||
ctx.HTML(http.StatusOK, tplDeleteFile)
|
||||
}
|
||||
|
||||
// DeletePath render delete file page
|
||||
func DeletePath(ctx *context.Context) {
|
||||
DeleteFile(ctx)
|
||||
}
|
||||
|
||||
// DeleteFilePost response for deleting file
|
||||
func DeleteFilePost(ctx *context.Context) {
|
||||
DeletePathOrFilePost(ctx, false)
|
||||
}
|
||||
|
||||
// DeletePathPost response for deleting path
|
||||
func DeletePathPost(ctx *context.Context) {
|
||||
DeletePathOrFilePost(ctx, true)
|
||||
}
|
||||
|
||||
// DeletePathOrFilePost response for deleting path or file
|
||||
func DeletePathOrFilePost(ctx *context.Context, isdir bool) {
|
||||
form := web.GetForm(ctx).(*forms.DeleteRepoFileForm)
|
||||
canCommit := renderCommitRights(ctx)
|
||||
branchName := ctx.Repo.BranchName
|
||||
|
@ -543,6 +558,7 @@ func DeleteFilePost(ctx *context.Context) {
|
|||
TreePath: ctx.Repo.TreePath,
|
||||
},
|
||||
},
|
||||
IsDir: isdir, // Add this flag to indicate directory deletion
|
||||
Message: message,
|
||||
Signoff: form.Signoff,
|
||||
Author: gitIdentity,
|
||||
|
@ -758,6 +774,7 @@ func UploadFilePost(ctx *context.Context) {
|
|||
TreePath: form.TreePath,
|
||||
Message: message,
|
||||
Files: form.Files,
|
||||
FullPaths: form.FullPaths,
|
||||
Signoff: form.Signoff,
|
||||
Author: gitIdentity,
|
||||
Committer: gitIdentity,
|
||||
|
|
|
@ -1219,6 +1219,27 @@ PostRecentBranchCheck:
|
|||
} else {
|
||||
ctx.Data["CodeSearchOptions"] = git.GrepSearchOptions
|
||||
}
|
||||
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("git_model.GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
|
||||
ctx.Data["CanDeleteFile"] = false
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
} else {
|
||||
ctx.Data["CanDeleteFile"] = true
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
||||
}
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
|
||||
}
|
||||
|
||||
isAnnexFile, okAnnexFile := ctx.Data["IsAnnexFile"]
|
||||
isAnnexFilePresent, okAnnexFilePresent := ctx.Data["IsAnnexFilePresent"]
|
||||
if okAnnexFile && okAnnexFilePresent && isAnnexFile.(bool) && !isAnnexFilePresent.(bool) {
|
||||
|
|
|
@ -51,6 +51,7 @@ import (
|
|||
|
||||
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
|
||||
|
||||
"code.forgejo.org/go-chi/binding"
|
||||
"code.forgejo.org/go-chi/captcha"
|
||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
|
@ -1268,11 +1269,13 @@ func registerRoutes(m *web.Route) {
|
|||
m.Combo("/_new/*").Get(repo.NewFile).
|
||||
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost)
|
||||
m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost)
|
||||
m.Combo("/_delete_path/*").Get(repo.DeletePath).
|
||||
Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeletePathPost)
|
||||
m.Combo("/_delete/*").Get(repo.DeleteFile).
|
||||
Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost)
|
||||
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
|
||||
Get(repo.UploadFile).
|
||||
Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
|
||||
Post(BindUpload(forms.UploadRepoFileForm{}), repo.UploadFilePost)
|
||||
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
|
||||
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
|
||||
m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick).
|
||||
|
@ -1694,3 +1697,20 @@ func registerRoutes(m *web.Route) {
|
|||
ctx.NotFound("", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func BindUpload(f forms.UploadRepoFileForm) http.HandlerFunc {
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
theObj := new(forms.UploadRepoFileForm) // create a new form obj for every request but not use obj directly
|
||||
data := middleware.GetContextData(req.Context())
|
||||
binding.Bind(req, theObj)
|
||||
files := theObj.Files
|
||||
var fullpaths []string
|
||||
for _, fileID := range files {
|
||||
fullPath := req.Form.Get("files_fullpath[" + fileID + "]")
|
||||
fullpaths = append(fullpaths, fullPath)
|
||||
}
|
||||
theObj.FullPaths = fullpaths
|
||||
data.GetData()["__form"] = theObj
|
||||
middleware.AssignForm(theObj, data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -671,6 +671,7 @@ type UploadRepoFileForm struct {
|
|||
CommitChoice string `binding:"Required;MaxSize(50)"`
|
||||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
Files []string
|
||||
FullPaths []string
|
||||
CommitMailID int64 `binding:"Required"`
|
||||
Signoff bool
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package files
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
@ -56,6 +57,7 @@ type ChangeRepoFilesOptions struct {
|
|||
Committer *IdentityOptions
|
||||
Dates *CommitDateOptions
|
||||
Signoff bool
|
||||
IsDir bool `default:"false"`
|
||||
}
|
||||
|
||||
type RepoFileOptions struct {
|
||||
|
@ -90,6 +92,29 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if opts.IsDir {
|
||||
var new_opts_files []*ChangeRepoFile
|
||||
for _, file := range opts.Files {
|
||||
if file.Operation != "delete" {
|
||||
return nil, errors.New("invalid operation: only delete is allowed for directory paths")
|
||||
}
|
||||
treePath := CleanUploadFileName(file.TreePath)
|
||||
filelist, err := gitRepo.LsFilesFromDirectory(treePath, opts.OldBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, filename := range filelist {
|
||||
if len(filename) > 0 {
|
||||
new_opts_files = append(new_opts_files, &ChangeRepoFile{
|
||||
Operation: "delete",
|
||||
TreePath: filename,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
opts.Files = new_opts_files
|
||||
}
|
||||
|
||||
var treePaths []string
|
||||
for _, file := range opts.Files {
|
||||
// If FromTreePath is not set, set it to the opts.TreePath
|
||||
|
|
|
@ -6,10 +6,12 @@ package files
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
|
@ -30,7 +32,8 @@ type UploadRepoFileOptions struct {
|
|||
Message string
|
||||
Author *IdentityOptions
|
||||
Committer *IdentityOptions
|
||||
Files []string // In UUID format.
|
||||
Files []string // In UUID format
|
||||
FullPaths []string
|
||||
Signoff bool
|
||||
}
|
||||
|
||||
|
@ -59,6 +62,10 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
return nil
|
||||
}
|
||||
|
||||
if len(opts.Files) != len(opts.FullPaths) {
|
||||
return nil
|
||||
}
|
||||
|
||||
uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
|
||||
|
@ -68,6 +75,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
infos := make([]uploadInfo, len(uploads))
|
||||
for i, upload := range uploads {
|
||||
// Check file is not lfs locked, will return nil if lock setting not enabled
|
||||
upload.Name = fileNameSanitize(html.UnescapeString(opts.FullPaths[i]))
|
||||
filepath := path.Join(opts.TreePath, upload.Name)
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, repo.ID, filepath)
|
||||
if err != nil {
|
||||
|
@ -178,6 +186,19 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||
return repo_model.DeleteUploads(ctx, uploads...)
|
||||
}
|
||||
|
||||
// From forgejo/services/repository/generate.go (but now allows /)
|
||||
var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`)
|
||||
|
||||
// Sanitize user input to valid OS filenames
|
||||
//
|
||||
// Based on https://github.com/sindresorhus/filename-reserved-regex
|
||||
// Adds ".." to prevent directory traversal
|
||||
func fileNameSanitize(s string) string {
|
||||
+ // Added this because I am not sure what Windows will deliver us \ or / but we need /.
|
||||
s = strings.ReplaceAll(s, "\\", "/")
|
||||
return strings.TrimSpace(fileNameSanitizeRegexp.ReplaceAllString(s, "_"))
|
||||
}
|
||||
|
||||
func copyUploadedLFSFilesIntoRepository(infos []uploadInfo, t *TemporaryUploadRepository, treePath string) error {
|
||||
var storeInLFSFunc func(string) (bool, error)
|
||||
|
||||
|
|
|
@ -11,41 +11,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.platform_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .LandingPageInfoEnabled}}
|
||||
{{template "landing-page" .}}
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
|
36
templates/landing-page.tmpl
Normal file
36
templates/landing-page.tmpl
Normal file
|
@ -0,0 +1,36 @@
|
|||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.platform_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -114,6 +114,15 @@
|
|||
{{- end -}}
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
{{if (not .IsViewFile)}}
|
||||
{{if and .CanDeleteFile (not .IsBlame)}}
|
||||
<a href="{{.RepoLink}}/_delete_path/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon btn-octicon-danger" data-tooltip-content="{{.DeleteFileTooltip}}">{{svg "octicon-trash"}}</span></a>
|
||||
{{else}}
|
||||
<span class="btn-octicon disabled" data-tooltip-content="{{.DeleteFileTooltip}}">{{svg "octicon-trash"}}</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<!-- Only show clone panel in repository home page -->
|
||||
|
|
|
@ -50,18 +50,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui container fluid">
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
{{if .SignInForgottenPasswordEnabled}}
|
||||
<div class="ui container fluid">
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
|
||||
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="field">
|
||||
{{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}}
|
||||
<br>
|
||||
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="field">
|
||||
{{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}}
|
||||
<br>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="field">
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="field">
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import $ from 'jquery';
|
||||
import '../vendor/jquery.are-you-sure.js';
|
||||
import {clippie} from 'clippie';
|
||||
import {createDropzone} from './dropzone.js';
|
||||
import {showGlobalErrorMessage} from '../bootstrap.js';
|
||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
|
||||
import {svg} from '../svg.js';
|
||||
import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js';
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {showTemporaryTooltip} from '../modules/tippy.js';
|
||||
import {confirmModal} from './comp/ConfirmModal.js';
|
||||
import {showErrorToast} from '../modules/toast.js';
|
||||
import {request, POST, GET} from '../modules/fetch.js';
|
||||
import { clippie } from 'clippie';
|
||||
import { createDropzone } from './dropzone.js';
|
||||
import { showGlobalErrorMessage } from '../bootstrap.js';
|
||||
import { handleGlobalEnterQuickSubmit } from './comp/QuickSubmit.js';
|
||||
import { svg } from '../svg.js';
|
||||
import { hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter } from '../utils/dom.js';
|
||||
import { htmlEscape } from 'escape-goat';
|
||||
import { showTemporaryTooltip } from '../modules/tippy.js';
|
||||
import { confirmModal } from './comp/ConfirmModal.js';
|
||||
import { showErrorToast } from '../modules/toast.js';
|
||||
import { request, POST, GET } from '../modules/fetch.js';
|
||||
import '../htmx.js';
|
||||
|
||||
const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
|
||||
const { appUrl, appSubUrl, csrfToken, i18n } = window.config;
|
||||
|
||||
export function initGlobalFormDirtyLeaveConfirm() {
|
||||
// Warn users that try to leave a page after entering data into a form.
|
||||
|
@ -82,7 +82,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
|
|||
try {
|
||||
const resp = await request(url, opt);
|
||||
if (resp.status === 200) {
|
||||
let {redirect} = await resp.json();
|
||||
let { redirect } = await resp.json();
|
||||
redirect = redirect || actionElem.getAttribute('data-redirect');
|
||||
actionElem.classList.remove('dirty'); // remove the areYouSure check before reloading
|
||||
if (redirect) {
|
||||
|
@ -96,7 +96,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
|
|||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||
if (data.errorMessage) {
|
||||
showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'});
|
||||
showErrorToast(data.errorMessage, { useHtmlBody: data.renderFormat === 'html' });
|
||||
} else {
|
||||
showErrorToast(`server error: ${resp.status}`);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ async function formFetchAction(e) {
|
|||
}
|
||||
|
||||
let reqUrl = formActionUrl;
|
||||
const reqOpt = {method: formMethod.toUpperCase()};
|
||||
const reqOpt = { method: formMethod.toUpperCase() };
|
||||
if (formMethod.toLowerCase() === 'get') {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of formData) {
|
||||
|
@ -212,7 +212,7 @@ export function initDropzone(el) {
|
|||
const $dropzone = $(el);
|
||||
const _promise = createDropzone(el, {
|
||||
url: $dropzone.data('upload-url'),
|
||||
headers: {'X-Csrf-Token': csrfToken},
|
||||
headers: { 'X-Csrf-Token': csrfToken },
|
||||
maxFiles: $dropzone.data('max-file'),
|
||||
maxFilesize: $dropzone.data('max-size'),
|
||||
acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
|
||||
|
@ -229,7 +229,8 @@ export function initDropzone(el) {
|
|||
this.on('success', (file, data) => {
|
||||
file.uuid = data.uuid;
|
||||
const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
$dropzone.find('.files').append($input);
|
||||
const $inputPath = $(`<input type="hidden" name="files_fullpath[${data.uuid}]" value="${htmlEscape(file.fullPath || file.name)}">`);
|
||||
$dropzone.find('.files').append($input).append($inputPath);
|
||||
// Create a "Copy Link" element, to conveniently copy the image
|
||||
// or file link as Markdown to the clipboard
|
||||
const copyLinkElement = document.createElement('div');
|
||||
|
@ -250,10 +251,15 @@ export function initDropzone(el) {
|
|||
file.previewTemplate.append(copyLinkElement);
|
||||
});
|
||||
this.on('removedfile', (file) => {
|
||||
// Remove the hidden input for the file
|
||||
$(`#${file.uuid}`).remove();
|
||||
|
||||
// Remove the hidden input for files_fullpath
|
||||
$(`input[name="files_fullpath[${file.uuid}]"]`).remove();
|
||||
|
||||
if ($dropzone.data('remove-url')) {
|
||||
POST($dropzone.data('remove-url'), {
|
||||
data: new URLSearchParams({file: file.uuid}),
|
||||
data: new URLSearchParams({ file: file.uuid }),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -276,7 +282,7 @@ async function linkAction(e) {
|
|||
const url = el.getAttribute('data-url');
|
||||
const doRequest = async () => {
|
||||
el.disabled = true;
|
||||
await fetchActionDoRequest(el, url, {method: 'POST'});
|
||||
await fetchActionDoRequest(el, url, { method: 'POST' });
|
||||
el.disabled = false;
|
||||
};
|
||||
|
||||
|
@ -287,7 +293,7 @@ async function linkAction(e) {
|
|||
}
|
||||
|
||||
const isRisky = el.classList.contains('red') || el.classList.contains('yellow') || el.classList.contains('orange') || el.classList.contains('negative');
|
||||
if (await confirmModal({content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'primary'})) {
|
||||
if (await confirmModal({ content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'primary' })) {
|
||||
await doRequest();
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +337,7 @@ export function initGlobalLinkActions() {
|
|||
}
|
||||
}
|
||||
|
||||
const response = await POST($this.data('url'), {data: postData});
|
||||
const response = await POST($this.data('url'), { data: postData });
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
window.location.href = data.redirect;
|
||||
|
|
Loading…
Add table
Reference in a new issue