feat(packages): add bulk visibility management for packages
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m12s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 5m8s
Build and Release / Lint (push) Successful in 5m19s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 3m11s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 5m38s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m9s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m29s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 10m38s
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m12s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 5m8s
Build and Release / Lint (push) Successful in 5m19s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 3m11s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 5m38s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m9s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m29s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 10m38s
Add ability to bulk set packages as private or public in both admin and repository package views. Includes new bulk action buttons, visibility grouping in repository view, and corresponding backend handlers for processing visibility changes. Admin can manage all packages while repository owners can manage their own packages.
This commit is contained in:
@@ -3322,6 +3322,13 @@
|
||||
"admin.packages.bulk.global.partial": "Enabled global access for %d package(s), %d failed (may already exist as global)",
|
||||
"admin.packages.bulk.automatch.success": "Auto-matched %d package(s) to repositories",
|
||||
"admin.packages.bulk.automatch.none": "No matching repositories found for selected packages",
|
||||
"admin.packages.bulk.make_private": "Make Private",
|
||||
"admin.packages.bulk.make_public": "Make Public",
|
||||
"admin.packages.bulk.private.enabled": "Made %d package(s) private",
|
||||
"admin.packages.bulk.private.disabled": "Made %d package(s) public",
|
||||
"admin.packages.visibility": "Visibility",
|
||||
"admin.packages.visibility.private": "Private",
|
||||
"admin.packages.visibility.public": "Public",
|
||||
"admin.packages.automatch.button": "Find matching repository",
|
||||
"admin.packages.automatch.match": "Match",
|
||||
"admin.packages.automatch.success": "Package linked to matching repository",
|
||||
@@ -3726,6 +3733,14 @@
|
||||
"packages.no_metadata": "No metadata.",
|
||||
"packages.empty.documentation": "For more information on the package registry, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the documentation</a>.",
|
||||
"packages.empty.repo": "Did you upload a package, but it's not shown here? Go to <a href=\"%[1]s\">package settings</a> and link it to this repo.",
|
||||
"packages.visibility.public": "Public Packages",
|
||||
"packages.visibility.private": "Private Packages",
|
||||
"packages.bulk.actions": "Bulk Actions",
|
||||
"packages.bulk.make_private": "Make Private",
|
||||
"packages.bulk.make_public": "Make Public",
|
||||
"packages.bulk.selected": "Selected:",
|
||||
"packages.bulk.select_all": "Select all",
|
||||
"packages.bulk.no_selection": "Please select at least one package.",
|
||||
"packages.registry.documentation": "For more information on the %s registry, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the documentation</a>.",
|
||||
"packages.filter.type": "Type",
|
||||
"packages.filter.type.all": "All",
|
||||
|
||||
@@ -177,6 +177,41 @@ func BulkAutoMatch(ctx *context.Context) {
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages")
|
||||
}
|
||||
|
||||
// BulkSetPrivate sets/unsets private flag on multiple packages
|
||||
func BulkSetPrivate(ctx *context.Context) {
|
||||
packageIDs := ctx.FormStrings("ids[]")
|
||||
isPrivate := ctx.FormBool("is_private")
|
||||
|
||||
ids := make([]int64, 0, len(packageIDs))
|
||||
for _, idStr := range packageIDs {
|
||||
var id int64
|
||||
if _, err := fmt.Sscanf(idStr, "%d", &id); err == nil && id > 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
ctx.Flash.Error(ctx.Tr("admin.packages.bulk.no_selection"))
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages")
|
||||
return
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
for _, id := range ids {
|
||||
if err := packages_model.SetPackageIsPrivate(ctx, id, isPrivate); err == nil {
|
||||
succeeded++
|
||||
}
|
||||
}
|
||||
|
||||
if isPrivate {
|
||||
ctx.Flash.Success(ctx.Tr("admin.packages.bulk.private.enabled", succeeded))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("admin.packages.bulk.private.disabled", succeeded))
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages")
|
||||
}
|
||||
|
||||
// SingleAutoMatch automatically matches a single package to a repository
|
||||
func SingleAutoMatch(ctx *context.Context) {
|
||||
packageID := ctx.FormInt64("id")
|
||||
|
||||
@@ -5,6 +5,7 @@ package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.gitcaddy.com/server/v3/models/db"
|
||||
"code.gitcaddy.com/server/v3/models/packages"
|
||||
@@ -19,7 +20,7 @@ const (
|
||||
tplPackagesList templates.TplName = "repo/packages"
|
||||
)
|
||||
|
||||
// Packages displays a list of all packages in the repository
|
||||
// Packages displays a list of all packages in the repository, grouped by visibility
|
||||
func Packages(ctx *context.Context) {
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
query := ctx.FormTrim("q")
|
||||
@@ -47,6 +48,16 @@ func Packages(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Group packages by visibility
|
||||
var publicPackages, privatePackages []*packages.PackageDescriptor
|
||||
for _, pd := range pds {
|
||||
if pd.Package.IsPrivate {
|
||||
privatePackages = append(privatePackages, pd)
|
||||
} else {
|
||||
publicPackages = append(publicPackages, pd)
|
||||
}
|
||||
}
|
||||
|
||||
hasPackages, err := packages.HasRepositoryPackages(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("HasRepositoryPackages", err)
|
||||
@@ -61,6 +72,8 @@ func Packages(ctx *context.Context) {
|
||||
ctx.Data["HasPackages"] = hasPackages
|
||||
ctx.Data["CanWritePackages"] = ctx.Repo.CanWrite(unit.TypePackages) || ctx.IsUserSiteAdmin()
|
||||
ctx.Data["PackageDescriptors"] = pds
|
||||
ctx.Data["PublicPackages"] = publicPackages
|
||||
ctx.Data["PrivatePackages"] = privatePackages
|
||||
ctx.Data["Total"] = total
|
||||
ctx.Data["RepositoryAccessMap"] = map[int64]bool{ctx.Repo.Repository.ID: true} // There is only the current repository
|
||||
|
||||
@@ -70,3 +83,34 @@ func Packages(ctx *context.Context) {
|
||||
|
||||
ctx.HTML(http.StatusOK, tplPackagesList)
|
||||
}
|
||||
|
||||
// BulkSetPackageVisibility changes visibility for multiple packages
|
||||
func BulkSetPackageVisibility(ctx *context.Context) {
|
||||
if !ctx.Repo.CanWrite(unit.TypePackages) && !ctx.IsUserSiteAdmin() {
|
||||
ctx.JSONError(ctx.Tr("packages.settings.visibility.no_permission"))
|
||||
return
|
||||
}
|
||||
|
||||
isPrivate := ctx.FormString("is_private") == "true"
|
||||
ids := ctx.Req.Form["ids[]"]
|
||||
|
||||
for _, idStr := range ids {
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify the package belongs to this repository
|
||||
pkg, err := packages.GetPackageByID(ctx, id)
|
||||
if err != nil || pkg.RepoID != ctx.Repo.Repository.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := packages.SetPackageIsPrivate(ctx, id, isPrivate); err != nil {
|
||||
ctx.ServerError("SetPackageIsPrivate", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
@@ -851,6 +851,7 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Post("/delete", admin.DeletePackageVersion)
|
||||
m.Post("/cleanup", admin.CleanupExpiredData)
|
||||
m.Post("/bulk-global", admin.BulkSetGlobal)
|
||||
m.Post("/bulk-private", admin.BulkSetPrivate)
|
||||
m.Post("/bulk-automatch", admin.BulkAutoMatch)
|
||||
m.Post("/automatch", admin.SingleAutoMatch)
|
||||
}, packagesEnabled)
|
||||
@@ -1638,6 +1639,7 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
if setting.Packages.Enabled {
|
||||
m.Get("/packages", repo.Packages)
|
||||
m.Post("/packages/bulk-visibility", reqSignIn, repo.BulkSetPackageVisibility)
|
||||
}
|
||||
}, optSignIn, context.RepoAssignment)
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<div class="item" data-action="enable-global">{{svg "octicon-globe" 14}} {{ctx.Locale.Tr "admin.packages.bulk.enable_global"}}</div>
|
||||
<div class="item" data-action="disable-global">{{svg "octicon-lock" 14}} {{ctx.Locale.Tr "admin.packages.bulk.disable_global"}}</div>
|
||||
<div class="divider"></div>
|
||||
<div class="item" data-action="make-private">{{svg "octicon-lock" 14}} {{ctx.Locale.Tr "admin.packages.bulk.make_private"}}</div>
|
||||
<div class="item" data-action="make-public">{{svg "octicon-eye" 14}} {{ctx.Locale.Tr "admin.packages.bulk.make_public"}}</div>
|
||||
<div class="divider"></div>
|
||||
<div class="item" data-action="automatch">{{svg "octicon-link" 14}} {{ctx.Locale.Tr "admin.packages.bulk.automatch"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,6 +61,7 @@
|
||||
</th>
|
||||
<th>{{ctx.Locale.Tr "admin.packages.creator"}}</th>
|
||||
<th>{{ctx.Locale.Tr "admin.packages.repository"}}</th>
|
||||
<th>{{ctx.Locale.Tr "admin.packages.visibility"}}</th>
|
||||
<th>{{ctx.Locale.Tr "admin.packages.global"}}</th>
|
||||
<th>{{ctx.Locale.Tr "admin.packages.size"}}</th>
|
||||
<th data-sortt-asc="created_asc" data-sortt-desc="created_desc">
|
||||
@@ -91,6 +95,13 @@
|
||||
</button>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .Package.IsPrivate}}
|
||||
<span class="ui tiny orange label">{{svg "octicon-lock" 12}} {{ctx.Locale.Tr "admin.packages.visibility.private"}}</span>
|
||||
{{else}}
|
||||
<span class="ui tiny label">{{svg "octicon-eye" 12}} {{ctx.Locale.Tr "admin.packages.visibility.public"}}</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .Package.IsGlobal}}
|
||||
<span class="ui tiny green label">{{svg "octicon-globe" 12}} {{ctx.Locale.Tr "admin.packages.global.yes"}}</span>
|
||||
@@ -108,7 +119,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td class="tw-text-center" colspan="12">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
|
||||
<tr><td class="tw-text-center" colspan="13">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -177,6 +188,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
url = '{{AppSubUrl}}/-/admin/packages/bulk-global';
|
||||
formData.append('is_global', 'false');
|
||||
break;
|
||||
case 'make-private':
|
||||
url = '{{AppSubUrl}}/-/admin/packages/bulk-private';
|
||||
formData.append('is_private', 'true');
|
||||
break;
|
||||
case 'make-public':
|
||||
url = '{{AppSubUrl}}/-/admin/packages/bulk-private';
|
||||
formData.append('is_private', 'false');
|
||||
break;
|
||||
case 'automatch':
|
||||
url = '{{AppSubUrl}}/-/admin/packages/bulk-automatch';
|
||||
break;
|
||||
|
||||
@@ -14,37 +14,125 @@
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
<div>
|
||||
{{range .PackageDescriptors}}
|
||||
<div class="flex-list">
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
<a href="{{.VersionWebLink}}">{{.Package.Name}}</a>
|
||||
<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
|
||||
{{if .Package.IsPrivate}}
|
||||
<span class="ui basic orange label">{{ctx.Locale.Tr "repo.visibility.private"}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="flex-item-body">
|
||||
{{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}}
|
||||
{{$hasRepositoryAccess := false}}
|
||||
{{if .Repository}}
|
||||
{{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}}
|
||||
{{end}}
|
||||
{{if $hasRepositoryAccess}}
|
||||
{{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<span class="text grey">{{DateUtils.TimeSince .Version.CreatedUnix}}</span>
|
||||
</div>
|
||||
|
||||
{{if and .CanWritePackages (or .PublicPackages .PrivatePackages)}}
|
||||
<div class="tw-flex tw-gap-2 tw-my-3">
|
||||
<div class="ui small dropdown button" id="pkg-bulk-actions">
|
||||
<span class="text">{{ctx.Locale.Tr "packages.bulk.actions"}}</span>
|
||||
{{svg "octicon-triangle-down" 14}}
|
||||
<div class="menu">
|
||||
<div class="item" data-action="make-private">{{svg "octicon-lock" 14}} {{ctx.Locale.Tr "packages.bulk.make_private"}}</div>
|
||||
<div class="item" data-action="make-public">{{svg "octicon-globe" 14}} {{ctx.Locale.Tr "packages.bulk.make_public"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<span class="tw-text-text-light-2 tw-self-center" id="pkg-selected-count"></span>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div>
|
||||
{{/* Public Packages Section */}}
|
||||
{{if .PublicPackages}}
|
||||
<div class="tw-mb-6">
|
||||
<h4 class="ui top attached header tw-flex tw-items-center tw-gap-2">
|
||||
{{svg "octicon-globe" 16}}
|
||||
{{ctx.Locale.Tr "packages.visibility.public"}}
|
||||
<span class="ui small label">{{len .PublicPackages}}</span>
|
||||
{{if .CanWritePackages}}
|
||||
<div class="tw-ml-auto">
|
||||
<input type="checkbox" class="select-section-packages" data-section="public" title="{{ctx.Locale.Tr "packages.bulk.select_all"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{range .PublicPackages}}
|
||||
<div class="flex-list">
|
||||
<div class="flex-item">
|
||||
{{if $.CanWritePackages}}
|
||||
<div class="flex-item-leading">
|
||||
<input type="checkbox" class="package-checkbox" data-package-id="{{.Package.ID}}" data-section="public">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
<a href="{{.VersionWebLink}}">{{.Package.Name}}</a>
|
||||
<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
|
||||
</div>
|
||||
<div class="flex-item-body">
|
||||
{{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}}
|
||||
{{$hasRepositoryAccess := false}}
|
||||
{{if .Repository}}
|
||||
{{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}}
|
||||
{{end}}
|
||||
{{if $hasRepositoryAccess}}
|
||||
{{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<span class="text grey">{{DateUtils.TimeSince .Version.CreatedUnix}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* Private Packages Section */}}
|
||||
{{if .PrivatePackages}}
|
||||
<div class="tw-mb-6">
|
||||
<h4 class="ui top attached header tw-flex tw-items-center tw-gap-2">
|
||||
{{svg "octicon-lock" 16}}
|
||||
{{ctx.Locale.Tr "packages.visibility.private"}}
|
||||
<span class="ui small orange label">{{len .PrivatePackages}}</span>
|
||||
{{if .CanWritePackages}}
|
||||
<div class="tw-ml-auto">
|
||||
<input type="checkbox" class="select-section-packages" data-section="private" title="{{ctx.Locale.Tr "packages.bulk.select_all"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{range .PrivatePackages}}
|
||||
<div class="flex-list">
|
||||
<div class="flex-item">
|
||||
{{if $.CanWritePackages}}
|
||||
<div class="flex-item-leading">
|
||||
<input type="checkbox" class="package-checkbox" data-package-id="{{.Package.ID}}" data-section="private">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
<a href="{{.VersionWebLink}}">{{.Package.Name}}</a>
|
||||
<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
|
||||
<span class="ui basic orange label">{{ctx.Locale.Tr "repo.visibility.private"}}</span>
|
||||
</div>
|
||||
<div class="flex-item-body">
|
||||
{{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}}
|
||||
{{$hasRepositoryAccess := false}}
|
||||
{{if .Repository}}
|
||||
{{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}}
|
||||
{{end}}
|
||||
{{if $hasRepositoryAccess}}
|
||||
{{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<span class="text grey">{{DateUtils.TimeSince .Version.CreatedUnix}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* Empty state - no packages at all */}}
|
||||
{{if and (not .PublicPackages) (not .PrivatePackages)}}
|
||||
{{if not .HasPackages}}
|
||||
<div class="empty-placeholder">
|
||||
{{svg "octicon-package" 48}}
|
||||
@@ -61,3 +149,76 @@
|
||||
{{end}}
|
||||
{{template "base/paginate" .}}
|
||||
</div>
|
||||
|
||||
{{if .CanWritePackages}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sectionCheckboxes = document.querySelectorAll('.select-section-packages');
|
||||
const packageCheckboxes = document.querySelectorAll('.package-checkbox');
|
||||
const selectedCountEl = document.getElementById('pkg-selected-count');
|
||||
const bulkDropdown = document.getElementById('pkg-bulk-actions');
|
||||
|
||||
function updateSelectedCount() {
|
||||
const selected = document.querySelectorAll('.package-checkbox:checked').length;
|
||||
if (selected > 0) {
|
||||
selectedCountEl.textContent = '{{ctx.Locale.Tr "packages.bulk.selected"}} ' + selected;
|
||||
} else {
|
||||
selectedCountEl.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Section checkbox toggles all packages in that section
|
||||
sectionCheckboxes.forEach(cb => {
|
||||
cb.addEventListener('change', function() {
|
||||
const section = this.dataset.section;
|
||||
document.querySelectorAll(`.package-checkbox[data-section="${section}"]`).forEach(pkg => {
|
||||
pkg.checked = this.checked;
|
||||
});
|
||||
updateSelectedCount();
|
||||
});
|
||||
});
|
||||
|
||||
packageCheckboxes.forEach(cb => {
|
||||
cb.addEventListener('change', updateSelectedCount);
|
||||
});
|
||||
|
||||
function getSelectedIds() {
|
||||
return Array.from(document.querySelectorAll('.package-checkbox:checked'))
|
||||
.map(cb => cb.dataset.packageId);
|
||||
}
|
||||
|
||||
bulkDropdown?.querySelectorAll('.item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
const action = this.dataset.action;
|
||||
const ids = getSelectedIds();
|
||||
if (ids.length === 0) {
|
||||
alert('{{ctx.Locale.Tr "packages.bulk.no_selection"}}');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
ids.forEach(id => formData.append('ids[]', id));
|
||||
|
||||
if (action === 'make-private') {
|
||||
formData.append('is_private', 'true');
|
||||
} else if (action === 'make-public') {
|
||||
formData.append('is_private', 'false');
|
||||
}
|
||||
|
||||
fetch(window.location.pathname + '/bulk-visibility', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Csrf-Token': document.querySelector('meta[name=_csrf]')?.content || ''
|
||||
}
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.ok) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user