From 25bd68b747ea76e714f919c596af3c2007ffc03e Mon Sep 17 00:00:00 2001 From: David Rotermund Date: Sun, 9 Feb 2025 00:45:35 +0100 Subject: [PATCH] davrot Mods --- modules/git/repo_index.go | 25 ++++++++++++++ modules/setting/service.go | 4 +++ routers/web/auth/auth.go | 3 ++ routers/web/home.go | 2 ++ routers/web/repo/editor.go | 17 ++++++++++ routers/web/repo/view.go | 21 ++++++++++++ routers/web/web.go | 22 +++++++++++- services/forms/repo_form.go | 1 + services/repository/files/update.go | 25 ++++++++++++++ services/repository/files/upload.go | 23 ++++++++++++- templates/home.tmpl | 41 +++-------------------- templates/landing-page.tmpl | 36 ++++++++++++++++++++ templates/repo/home.tmpl | 9 +++++ templates/user/auth/signin_inner.tmpl | 29 ++++++++-------- web_src/js/features/common-global.js | 48 +++++++++++++++------------ 15 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 templates/landing-page.tmpl diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index f45b6e6..d7a3790 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -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() diff --git a/modules/setting/service.go b/modules/setting/service.go index 74ed5cd..b877fbd 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -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) diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index ccab47a..968f596 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -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 { diff --git a/routers/web/home.go b/routers/web/home.go index d4be093..3fa2524 100644 --- a/routers/web/home.go +++ b/routers/web/home.go @@ -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) diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index f27ad62..2a43039 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -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, diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6329f5d..a3976ec 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -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) { diff --git a/routers/web/web.go b/routers/web/web.go index db0015f..a276fa2 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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) + } +} diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 1ce9b29..75936f8 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -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 } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index d6025b6..2622387 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -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 diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 21cd5a8..18b67ea 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -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) diff --git a/templates/home.tmpl b/templates/home.tmpl index a974344..5f84fca 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -11,41 +11,10 @@ -
-
-

- {{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}} -

-

- {{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}} -

-
-
-

- {{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}} -

-

- {{ctx.Locale.Tr "startpage.platform_desc"}} -

-
-
-
-
-

- {{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}} -

-

- {{ctx.Locale.Tr "startpage.lightweight_desc"}} -

-
-
-

- {{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}} -

-

- {{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}} -

-
-
+ + {{if .LandingPageInfoEnabled}} + {{template "landing-page" .}} + {{end}} + {{template "base/footer" .}} diff --git a/templates/landing-page.tmpl b/templates/landing-page.tmpl new file mode 100644 index 0000000..5eb6822 --- /dev/null +++ b/templates/landing-page.tmpl @@ -0,0 +1,36 @@ +
+
+

+ {{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}} +

+

+ {{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}} +

+
+
+

+ {{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}} +

+

+ {{ctx.Locale.Tr "startpage.platform_desc"}} +

+
+
+
+
+

+ {{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}} +

+

+ {{ctx.Locale.Tr "startpage.lightweight_desc"}} +

+
+
+

+ {{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}} +

+

+ {{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}} +

+
+
\ No newline at end of file diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 7bf4ee4..7b98665 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -114,6 +114,15 @@ {{- end -}} {{end}} + + {{if (not .IsViewFile)}} + {{if and .CanDeleteFile (not .IsBlame)}} + {{svg "octicon-trash"}} + {{else}} + {{svg "octicon-trash"}} + {{end}} + {{end}} +
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index ddef34f..6862f5c 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -50,18 +50,21 @@
-
- {{template "user/auth/webauthn_error" .}} +{{if .SignInForgottenPasswordEnabled}} +
+ {{template "user/auth/webauthn_error" .}} -
- {{if .ShowRegistrationButton}} -
- {{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}} -
+
+ {{if .ShowRegistrationButton}} +
+ {{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}} +
+
+ {{end}} +
- {{end}} - -
-
+
+{{end}} + diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 5a304d9..ded82ad 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -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 = $(``).val(data.uuid); - $dropzone.find('.files').append($input); + const $inputPath = $(``); + $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;