diff --git a/README.md b/README.md new file mode 100644 index 0000000..c28545f --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# Musenalm + +Bibliographie deutscher Almanache des 18. und 19. Jahrhunderts. Runs as a PocketBase-based web app with SQLite storage. + +## Commands + +Local build (dev assets + sqlite tags used in this repo): +```bash +./scripts/build.sh +``` + +Manual build equivalent: +```bash +go build -tags=dev,fts5,sqlite_icu -o ./tmp/musenalm . +``` + +Run (as used in the Docker image): +```bash +./scripts/run.sh +``` + +Docker image build: +```bash +./scripts/docker.sh +``` + +Reset local data directory: +```bash +./scripts/reset.sh +``` + +Docker Compose (prod/stage): +```bash +docker compose up --build +docker compose -f stage.docker-compose.yml up --build +``` + +## Build prerequisites + +- Go 1.24 (see `go.mod`, also used in `Dockerfile`). +- SQLite build tags used in this repo: + - Local/dev build: `dev,fts5,sqlite_icu` (see `scripts/build.sh`). + - Docker build: `sqlite_fts5,sqlite_json,sqlite_foreign_keys,sqlite_vtable` (see `Dockerfile`). +- The `dev` build tag serves templates/assets from disk (no embedding). Without it, assets are embedded into the binary. + +## Configuration + +Config files are loaded in this order, later values override earlier ones: +1) `config.dev.json` (dev defaults) +2) `config.json` (runtime overrides) +3) Environment variables with prefix `MUSENALM_` + +### Options + +`debug` (bool) +Enables PocketBase dev mode and, unless `disable_watchers` is true, turns on template watchers. + +`allow_test_login` (bool) +Creates/keeps a test superuser account (`demo@example.com` / `password`). When false, the test account is removed if present. + +`disable_watchers` (bool) +Disables template watchers even if `debug` is true. + +Environment variable equivalents: +- `MUSENALM_DEBUG` +- `MUSENALM_ALLOW_TEST_LOGIN` +- `MUSENALM_DISABLE_WATCHERS` + +## Hosting + +### Docker (recommended) + +`docker-compose.yml` runs the app on port 8090 and persists data in `/app/data/pb_data` via an external volume. + +Prereqs: +```bash +docker volume create musenalm +docker network create caddynet +``` + +Start: +```bash +docker compose up --build +``` + +Stage uses `stage.docker-compose.yml` and a separate external volume `musenalmstage`. + +### Bare binary + +Build the binary, then run it with a data directory: +```bash +./tmp/musenalm serve --http=0.0.0.0:8090 --dir=/path/to/pb_data +``` + diff --git a/controllers/exports_admin.go b/controllers/exports_admin.go new file mode 100644 index 0000000..c524b6f --- /dev/null +++ b/controllers/exports_admin.go @@ -0,0 +1,337 @@ +package controllers + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/Theodor-Springmann-Stiftung/musenalm/app" + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/Theodor-Springmann-Stiftung/musenalm/helpers/exports" + "github.com/Theodor-Springmann-Stiftung/musenalm/middleware" + "github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels" + "github.com/Theodor-Springmann-Stiftung/musenalm/templating" + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/router" + "github.com/pocketbase/pocketbase/tools/types" +) + +const ( + URL_EXPORTS_ADMIN = "/redaktion/exports/" + URL_EXPORTS_RUN = "run/" + URL_EXPORTS_LIST = "list/" + URL_EXPORTS_DOWNLOAD = "download/" + URL_EXPORTS_DELETE = "delete/" + TEMPLATE_EXPORTS = "/redaktion/exports/" + TEMPLATE_EXPORTS_LIST = "/redaktion/exports/list/" + LAYOUT_EXPORTS_FRAGMENT = "fragment" +) + +func init() { + app.Register(&ExportsAdmin{}) +} + +type ExportsAdmin struct{} + +func (p *ExportsAdmin) Up(ia pagemodels.IApp, engine *templating.Engine) error { + return nil +} + +func (p *ExportsAdmin) Down(ia pagemodels.IApp, engine *templating.Engine) error { + return nil +} + +func (p *ExportsAdmin) Setup(router *router.Router[*core.RequestEvent], ia pagemodels.IApp, engine *templating.Engine) error { + appInstance := ia.Core() + exports.StartCleanup(appInstance, 12*time.Hour) + + rg := router.Group(URL_EXPORTS_ADMIN) + rg.BindFunc(middleware.Authenticated(appInstance)) + rg.BindFunc(middleware.IsAdmin()) + rg.GET("", p.pageHandler(engine, appInstance)) + rg.GET(URL_EXPORTS_LIST, p.listHandler(engine, appInstance)) + rg.POST(URL_EXPORTS_RUN, p.runHandler(appInstance)) + rg.GET(URL_EXPORTS_DOWNLOAD+"{id}", p.downloadHandler(appInstance)) + rg.POST(URL_EXPORTS_DELETE+"{id}", p.deleteHandler(appInstance)) + return nil +} + +type exportView struct { + Id string + Name string + Filename string + Type string + Status string + Progress int + TablesTotal int + TablesDone int + SizeBytes int64 + SizeLabel string + Created types.DateTime + Expires types.DateTime + CurrentTable string + Error string +} + +func (p *ExportsAdmin) pageHandler(engine *templating.Engine, app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + data, err := exportsData(e, app) + if err != nil { + return engine.Response500(e, err, nil) + } + return engine.Response200(e, TEMPLATE_EXPORTS, data, pagemodels.LAYOUT_LOGIN_PAGES) + } +} + +func (p *ExportsAdmin) listHandler(engine *templating.Engine, app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + data, err := exportsData(e, app) + if err != nil { + return engine.Response500(e, err, nil) + } + return engine.Response200(e, TEMPLATE_EXPORTS_LIST, data, LAYOUT_EXPORTS_FRAGMENT) + } +} + +func (p *ExportsAdmin) runHandler(app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + req := templating.NewRequest(e) + if err := e.Request.ParseForm(); err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Formulardaten ungueltig."}) + } + csrfToken := e.Request.FormValue("csrf_token") + if err := req.CheckCSRF(csrfToken); err != nil { + session := req.Session() + if session == nil { + app.Logger().Warn("Export CSRF failed: no session", "token_len", len(csrfToken)) + } else { + app.Logger().Warn( + "Export CSRF failed", + "token_len", len(csrfToken), + "session_token_len", len(session.Token), + "session_csrf_len", len(session.CSRF), + "matches_token", csrfToken == session.Token, + "matches_csrf", csrfToken == session.CSRF, + ) + } + return e.JSON(http.StatusUnauthorized, map[string]any{"error": err.Error()}) + } + + exportType := strings.TrimSpace(e.Request.FormValue("export_type")) + if exportType == "" { + exportType = dbmodels.EXPORT_TYPE_DATA + } + if exportType != dbmodels.EXPORT_TYPE_DATA && exportType != dbmodels.EXPORT_TYPE_FILES { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Unbekannter Export-Typ."}) + } + + collection, err := app.FindCollectionByNameOrId(dbmodels.EXPORTS_TABLE) + if err != nil { + return e.JSON(http.StatusInternalServerError, map[string]any{"error": "Export-Tabelle nicht verfuegbar."}) + } + + record := core.NewRecord(collection) + now := time.Now() + var name string + if exportType == dbmodels.EXPORT_TYPE_FILES { + name = fmt.Sprintf("Dateien-Backup %s", now.Format("02.01.2006 15:04")) + } else { + name = fmt.Sprintf("XML-Export %s", now.Format("02.01.2006 15:04")) + } + record.Set(dbmodels.EXPORT_NAME_FIELD, name) + record.Set(dbmodels.EXPORT_TYPE_FIELD, exportType) + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_QUEUED) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 0) + record.Set(dbmodels.EXPORT_TABLES_TOTAL_FIELD, 0) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, 0) + record.Set(dbmodels.EXPORT_EXPIRES_FIELD, types.NowDateTime().Add(7*24*time.Hour)) + record.Set(dbmodels.EXPORT_ERROR_FIELD, "") + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + + if user := req.User(); user != nil { + record.Set(dbmodels.EXPORT_CREATED_BY_FIELD, user.Id) + } + + if err := app.Save(record); err != nil { + return e.JSON(http.StatusInternalServerError, map[string]any{"error": err.Error()}) + } + + go func(exportID string) { + var runErr error + if exportType == dbmodels.EXPORT_TYPE_FILES { + runErr = exports.RunFiles(app, exportID) + } else { + runErr = exports.Run(app, exportID) + } + if runErr != nil { + app.Logger().Error("Export failed", "error", runErr, "export_id", exportID) + } + }(record.Id) + + return e.JSON(http.StatusOK, map[string]any{"success": true, "id": record.Id}) + } +} + +func (p *ExportsAdmin) downloadHandler(app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + id := strings.TrimSpace(e.Request.PathValue("id")) + if id == "" { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Ungueltige Export-ID."}) + } + + record, err := app.FindRecordById(dbmodels.EXPORTS_TABLE, id) + if err != nil || record == nil { + return e.JSON(http.StatusNotFound, map[string]any{"error": "Export nicht gefunden."}) + } + + if record.GetString(dbmodels.EXPORT_STATUS_FIELD) != dbmodels.EXPORT_STATUS_COMPLETE { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Export ist noch nicht fertig."}) + } + + exportDir, err := exports.ExportDir(app) + if err != nil { + return e.JSON(http.StatusInternalServerError, map[string]any{"error": "Export-Verzeichnis nicht verfuegbar."}) + } + + filename := record.GetString(dbmodels.EXPORT_FILENAME_FIELD) + if filename == "" { + filename = record.Id + ".zip" + } + filename = filepath.Base(filename) + + path := filepath.Join(exportDir, filename) + if _, err := os.Stat(path); err != nil { + return e.JSON(http.StatusNotFound, map[string]any{"error": "Exportdatei nicht gefunden."}) + } + + e.Response.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + return e.FileFS(os.DirFS(exportDir), filename) + } +} + +func (p *ExportsAdmin) deleteHandler(app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + req := templating.NewRequest(e) + if err := e.Request.ParseForm(); err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Formulardaten ungueltig."}) + } + if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil { + return e.JSON(http.StatusUnauthorized, map[string]any{"error": err.Error()}) + } + + id := strings.TrimSpace(e.Request.PathValue("id")) + if id == "" { + return e.JSON(http.StatusBadRequest, map[string]any{"error": "Ungueltige Export-ID."}) + } + + record, err := app.FindRecordById(dbmodels.EXPORTS_TABLE, id) + if err != nil || record == nil { + return e.JSON(http.StatusNotFound, map[string]any{"error": "Export nicht gefunden."}) + } + + status := record.GetString(dbmodels.EXPORT_STATUS_FIELD) + if status == dbmodels.EXPORT_STATUS_RUNNING || status == dbmodels.EXPORT_STATUS_QUEUED { + return e.JSON(http.StatusConflict, map[string]any{"error": "Export laeuft noch."}) + } + + exportDir, err := exports.ExportDir(app) + if err == nil { + filename := record.GetString(dbmodels.EXPORT_FILENAME_FIELD) + if filename == "" { + filename = record.Id + ".xml" + } + filename = filepath.Base(filename) + _ = os.Remove(filepath.Join(exportDir, filename)) + _ = os.Remove(filepath.Join(exportDir, filename+".tmp")) + } + + if err := app.Delete(record); err != nil { + return e.JSON(http.StatusInternalServerError, map[string]any{"error": err.Error()}) + } + + return e.JSON(http.StatusOK, map[string]any{"success": true, "message": "Export geloescht."}) + } +} + +func exportsData(e *core.RequestEvent, app core.App) (map[string]any, error) { + data := map[string]any{} + req := templating.NewRequest(e) + + exportsList, hasRunning, err := loadExports(app) + if err != nil { + return nil, err + } + + data["exports"] = exportsList + data["has_running"] = hasRunning + data["csrf_token"] = "" + if req.Session() != nil { + data["csrf_token"] = req.Session().Token + } + return data, nil +} + +func loadExports(app core.App) ([]exportView, bool, error) { + records := []*core.Record{} + err := app.RecordQuery(dbmodels.EXPORTS_TABLE). + OrderBy(dbmodels.CREATED_FIELD + " DESC"). + All(&records) + if err != nil { + return nil, false, err + } + + exportsList := make([]exportView, 0, len(records)) + hasRunning := false + for _, record := range records { + status := record.GetString(dbmodels.EXPORT_STATUS_FIELD) + if status == dbmodels.EXPORT_STATUS_RUNNING || status == dbmodels.EXPORT_STATUS_QUEUED { + hasRunning = true + } + exportType := record.GetString(dbmodels.EXPORT_TYPE_FIELD) + if exportType == "" { + exportType = dbmodels.EXPORT_TYPE_DATA + } + sizeBytes := int64(record.GetInt(dbmodels.EXPORT_SIZE_FIELD)) + exportsList = append(exportsList, exportView{ + Id: record.Id, + Name: record.GetString(dbmodels.EXPORT_NAME_FIELD), + Filename: record.GetString(dbmodels.EXPORT_FILENAME_FIELD), + Type: exportType, + Status: status, + Progress: record.GetInt(dbmodels.EXPORT_PROGRESS_FIELD), + TablesTotal: record.GetInt(dbmodels.EXPORT_TABLES_TOTAL_FIELD), + TablesDone: record.GetInt(dbmodels.EXPORT_TABLES_DONE_FIELD), + SizeBytes: sizeBytes, + SizeLabel: formatBytes(sizeBytes), + Created: record.GetDateTime(dbmodels.CREATED_FIELD), + Expires: record.GetDateTime(dbmodels.EXPORT_EXPIRES_FIELD), + CurrentTable: record.GetString(dbmodels.EXPORT_CURRENT_TABLE_FIELD), + Error: record.GetString(dbmodels.EXPORT_ERROR_FIELD), + }) + } + + return exportsList, hasRunning, nil +} + +func formatBytes(size int64) string { + if size <= 0 { + return "0 B" + } + units := []string{"B", "KB", "MB", "GB", "TB"} + value := float64(size) + unitIdx := 0 + for value >= 1024 && unitIdx < len(units)-1 { + value /= 1024 + unitIdx++ + } + if value >= 100 { + return fmt.Sprintf("%.0f %s", value, units[unitIdx]) + } + if value >= 10 { + return fmt.Sprintf("%.1f %s", value, units[unitIdx]) + } + return fmt.Sprintf("%.2f %s", value, units[unitIdx]) +} diff --git a/dbmodels/db_data.go b/dbmodels/db_data.go index 6eae388..f588cb9 100644 --- a/dbmodels/db_data.go +++ b/dbmodels/db_data.go @@ -444,6 +444,7 @@ const ( FILES_TABLE = "files" HTML_TABLE = "html" PAGES_TABLE = "pages" + EXPORTS_TABLE = "exports" ID_FIELD = "id" CREATED_FIELD = "created" @@ -499,7 +500,7 @@ const ( REFERENCES_FIELD = "refs" URI_FIELD = "uri" - URL_FIELD ="url" + URL_FIELD = "url" MUSENALM_BAENDE_STATUS_FIELD = "musenalm_status" MUSENALM_INHALTE_TYPE_FIELD = "musenalm_type" @@ -555,5 +556,18 @@ const ( LAYOUT_FIELD = "layout" TYPE_FIELD = "type" + EXPORT_NAME_FIELD = "name" + EXPORT_FILENAME_FIELD = "filename" + EXPORT_TYPE_FIELD = "export_type" + EXPORT_STATUS_FIELD = "status" + EXPORT_PROGRESS_FIELD = "progress" + EXPORT_TABLES_TOTAL_FIELD = "tables_total" + EXPORT_TABLES_DONE_FIELD = "tables_done" + EXPORT_SIZE_FIELD = "size_bytes" + EXPORT_CURRENT_TABLE_FIELD = "current_table" + EXPORT_ERROR_FIELD = "error" + EXPORT_EXPIRES_FIELD = "expires_at" + EXPORT_CREATED_BY_FIELD = "created_by" + SESSION_COOKIE_NAME = "sid" ) diff --git a/dbmodels/exports.go b/dbmodels/exports.go new file mode 100644 index 0000000..4fa118d --- /dev/null +++ b/dbmodels/exports.go @@ -0,0 +1,23 @@ +package dbmodels + +const ( + EXPORT_STATUS_QUEUED = "queued" + EXPORT_STATUS_RUNNING = "running" + EXPORT_STATUS_COMPLETE = "complete" + EXPORT_STATUS_FAILED = "failed" + + EXPORT_TYPE_DATA = "data" + EXPORT_TYPE_FILES = "files" +) + +var EXPORT_STATUS_VALUES = []string{ + EXPORT_STATUS_QUEUED, + EXPORT_STATUS_RUNNING, + EXPORT_STATUS_COMPLETE, + EXPORT_STATUS_FAILED, +} + +var EXPORT_TYPE_VALUES = []string{ + EXPORT_TYPE_DATA, + EXPORT_TYPE_FILES, +} diff --git a/helpers/exports/cleanup.go b/helpers/exports/cleanup.go new file mode 100644 index 0000000..0a2c101 --- /dev/null +++ b/helpers/exports/cleanup.go @@ -0,0 +1,67 @@ +package exports + +import ( + "os" + "path/filepath" + "sync" + "time" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/types" +) + +var cleanupOnce sync.Once + +func StartCleanup(app core.App, interval time.Duration) { + if interval <= 0 { + interval = 12 * time.Hour + } + + cleanupOnce.Do(func() { + go func() { + CleanupExpired(app) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for range ticker.C { + CleanupExpired(app) + } + }() + }) +} + +func CleanupExpired(app core.App) { + now := types.NowDateTime() + records := []*core.Record{} + err := app.RecordQuery(dbmodels.EXPORTS_TABLE). + Where(dbx.NewExp(dbmodels.EXPORT_EXPIRES_FIELD+" <= {:now}", dbx.Params{"now": now})). + All(&records) + if err != nil { + app.Logger().Error("Export cleanup query failed", "error", err) + return + } + + if len(records) == 0 { + return + } + + exportDir, err := ExportDir(app) + if err != nil { + app.Logger().Error("Export cleanup dir failed", "error", err) + return + } + + for _, record := range records { + filename := record.GetString(dbmodels.EXPORT_FILENAME_FIELD) + if filename == "" { + filename = record.Id + ".xml" + } + filename = filepath.Base(filename) + _ = os.Remove(filepath.Join(exportDir, filename)) + _ = os.Remove(filepath.Join(exportDir, filename+".tmp")) + if err := app.Delete(record); err != nil { + app.Logger().Error("Export cleanup delete failed", "error", err, "export_id", record.Id) + } + } +} diff --git a/helpers/exports/exporter.go b/helpers/exports/exporter.go new file mode 100644 index 0000000..bbf022f --- /dev/null +++ b/helpers/exports/exporter.go @@ -0,0 +1,302 @@ +package exports + +import ( + "archive/zip" + "encoding/xml" + "os" + "path/filepath" + "strings" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/core" +) + +const exportDirName = "exports" + +func ExportDir(app core.App) (string, error) { + base := filepath.Join(app.DataDir(), exportDirName) + if err := os.MkdirAll(base, 0o755); err != nil { + return "", err + } + return base, nil +} + +func ListTables(app core.App) ([]string, error) { + tables := []string{} + err := app.DB(). + NewQuery("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"). + Column(&tables) + if err != nil { + return nil, err + } + + excluded := map[string]struct{}{ + "_superusers": {}, + "_mfas": {}, + "_otps": {}, + "_externalAuths": {}, + "_authorigins": {}, + "_authOrigins": {}, + "access_tokens": {}, + } + + filtered := tables[:0] + for _, table := range tables { + if strings.HasPrefix(table, dbmodels.FTS5_PREFIX) { + continue + } + if _, ok := excluded[table]; ok { + continue + } + filtered = append(filtered, table) + } + return filtered, nil +} + +func Run(app core.App, exportID string) error { + record, err := app.FindRecordById(dbmodels.EXPORTS_TABLE, exportID) + if err != nil { + return err + } + + tables, err := ListTables(app) + if err != nil { + return markFailed(app, record, err) + } + + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_RUNNING) + record.Set(dbmodels.EXPORT_TABLES_TOTAL_FIELD, len(tables)) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, 0) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 0) + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + record.Set(dbmodels.EXPORT_ERROR_FIELD, "") + if err := app.Save(record); err != nil { + return err + } + + exportDir, err := ExportDir(app) + if err != nil { + return markFailed(app, record, err) + } + + filename := exportID + ".zip" + tempPath := filepath.Join(exportDir, filename+".tmp") + finalPath := filepath.Join(exportDir, filename) + + file, err := os.Create(tempPath) + if err != nil { + return markFailed(app, record, err) + } + defer func() { + if file != nil { + _ = file.Close() + } + }() + + zipWriter := zip.NewWriter(file) + + for idx, table := range tables { + updateProgress(app, record, table, idx, len(tables)) + + if err := exportTableZipEntry(app, zipWriter, table); err != nil { + return markFailed(app, record, err) + } + + updateProgress(app, record, table, idx+1, len(tables)) + } + + if err := zipWriter.Close(); err != nil { + return markFailed(app, record, err) + } + if err := file.Close(); err != nil { + return markFailed(app, record, err) + } + file = nil + + if err := os.Rename(tempPath, finalPath); err != nil { + return markFailed(app, record, err) + } + + stat, err := os.Stat(finalPath) + if err != nil { + return markFailed(app, record, err) + } + + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_COMPLETE) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 100) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, len(tables)) + record.Set(dbmodels.EXPORT_FILENAME_FIELD, filename) + record.Set(dbmodels.EXPORT_SIZE_FIELD, stat.Size()) + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + record.Set(dbmodels.EXPORT_ERROR_FIELD, "") + if err := app.Save(record); err != nil { + return err + } + + return nil +} + +func exportTableZipEntry(app core.App, zipWriter *zip.Writer, table string) error { + entryName := safeFilename(table) + if entryName == "" { + entryName = "table" + } + entryName += ".xml" + + entry, err := zipWriter.Create(entryName) + if err != nil { + return err + } + if _, err := entry.Write([]byte(xml.Header)); err != nil { + return err + } + + encoder := xml.NewEncoder(entry) + start := xml.StartElement{ + Name: xml.Name{Local: "table"}, + Attr: []xml.Attr{{Name: xml.Name{Local: "name"}, Value: table}}, + } + if err := encoder.EncodeToken(start); err != nil { + return err + } + + query := "SELECT * FROM " + quoteTableName(table) + rows, err := app.DB().NewQuery(query).Rows() + if err != nil { + return err + } + defer rows.Close() + + columns, err := rows.Columns() + if err != nil { + return err + } + + for rows.Next() { + rowData := dbx.NullStringMap{} + if err := rows.ScanMap(rowData); err != nil { + return err + } + + if err := encoder.EncodeToken(xml.StartElement{Name: xml.Name{Local: "row"}}); err != nil { + return err + } + + sensitiveFields := map[string]struct{}{} + if table == "users" { + sensitiveFields = map[string]struct{}{ + "password": {}, + "password_hash": {}, + "passwordhash": {}, + "tokenkey": {}, + "token_key": {}, + } + } + + for _, col := range columns { + lowerCol := strings.ToLower(col) + if _, ok := sensitiveFields[lowerCol]; ok { + if err := encoder.EncodeToken(xml.StartElement{Name: xml.Name{Local: col}, Attr: []xml.Attr{{Name: xml.Name{Local: "null"}, Value: "true"}}}); err != nil { + return err + } + if err := encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: col}}); err != nil { + return err + } + continue + } + + value := rowData[col] + attrs := []xml.Attr{} + if !value.Valid { + attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "null"}, Value: "true"}) + } + + if err := encoder.EncodeToken(xml.StartElement{Name: xml.Name{Local: col}, Attr: attrs}); err != nil { + return err + } + if value.Valid { + if err := encoder.EncodeToken(xml.CharData([]byte(value.String))); err != nil { + return err + } + } + if err := encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: col}}); err != nil { + return err + } + } + + if err := encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: "row"}}); err != nil { + return err + } + } + + if err := rows.Err(); err != nil { + return err + } + + if err := encoder.EncodeToken(start.End()); err != nil { + return err + } + + return encoder.Flush() +} + +func updateProgress(app core.App, record *core.Record, table string, done, total int) { + progress := 0 + if total > 0 { + progress = int(float64(done) / float64(total) * 100) + } + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, table) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, done) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, progress) + if err := app.Save(record); err != nil { + app.Logger().Error("Export progress update failed", "error", err, "export_id", record.Id) + } +} + +func markFailed(app core.App, record *core.Record, err error) error { + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_FAILED) + record.Set(dbmodels.EXPORT_ERROR_FIELD, err.Error()) + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 0) + if saveErr := app.Save(record); saveErr != nil { + return saveErr + } + + exportDir, dirErr := ExportDir(app) + if dirErr == nil { + filename := record.GetString(dbmodels.EXPORT_FILENAME_FIELD) + if filename == "" { + filename = record.Id + ".zip" + } + filename = filepath.Base(filename) + _ = os.Remove(filepath.Join(exportDir, filename)) + _ = os.Remove(filepath.Join(exportDir, filename+".tmp")) + } + + return err +} + +func quoteTableName(name string) string { + return "`" + strings.ReplaceAll(name, "`", "``") + "`" +} + +func safeFilename(name string) string { + name = strings.TrimSpace(name) + if name == "" { + return "" + } + var b strings.Builder + b.Grow(len(name)) + for _, r := range name { + if (r >= 'a' && r <= 'z') || + (r >= 'A' && r <= 'Z') || + (r >= '0' && r <= '9') || + r == '_' || r == '-' || r == '.' { + b.WriteRune(r) + } else { + b.WriteByte('_') + } + } + return b.String() +} diff --git a/helpers/exports/file_exporter.go b/helpers/exports/file_exporter.go new file mode 100644 index 0000000..2b6123b --- /dev/null +++ b/helpers/exports/file_exporter.go @@ -0,0 +1,254 @@ +package exports + +import ( + "archive/zip" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/pocketbase/core" +) + +type fileEntry struct { + CollectionName string + CollectionId string + RecordId string + FieldName string + Filename string +} + +func RunFiles(app core.App, exportID string) error { + record, err := app.FindRecordById(dbmodels.EXPORTS_TABLE, exportID) + if err != nil { + return err + } + + files, err := collectFileEntries(app) + if err != nil { + return markFailed(app, record, err) + } + + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_RUNNING) + record.Set(dbmodels.EXPORT_TABLES_TOTAL_FIELD, len(files)) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, 0) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 0) + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + record.Set(dbmodels.EXPORT_ERROR_FIELD, "") + record.Set(dbmodels.EXPORT_FILENAME_FIELD, exportID+"-files.zip") + if err := app.Save(record); err != nil { + return err + } + + exportDir, err := ExportDir(app) + if err != nil { + return markFailed(app, record, err) + } + + filename := exportID + "-files.zip" + tempPath := filepath.Join(exportDir, filename+".tmp") + finalPath := filepath.Join(exportDir, filename) + + file, err := os.Create(tempPath) + if err != nil { + return markFailed(app, record, err) + } + defer func() { + if file != nil { + _ = file.Close() + } + }() + + zipWriter := zip.NewWriter(file) + + missing := 0 + lastProgressSave := time.Now() + for idx, entry := range files { + label := entry.CollectionName + "/" + entry.RecordId + "/" + entry.Filename + if shouldUpdateProgress(idx, len(files), lastProgressSave) { + updateProgress(app, record, label, idx, len(files)) + lastProgressSave = time.Now() + } + + if err := addFileToZip(app, zipWriter, entry); err != nil { + if os.IsNotExist(err) { + missing++ + continue + } + return markFailed(app, record, err) + } + + if shouldUpdateProgress(idx+1, len(files), lastProgressSave) { + updateProgress(app, record, label, idx+1, len(files)) + lastProgressSave = time.Now() + } + } + + if err := zipWriter.Close(); err != nil { + return markFailed(app, record, err) + } + if err := file.Close(); err != nil { + return markFailed(app, record, err) + } + file = nil + + if err := os.Rename(tempPath, finalPath); err != nil { + return markFailed(app, record, err) + } + + stat, err := os.Stat(finalPath) + if err != nil { + return markFailed(app, record, err) + } + + record.Set(dbmodels.EXPORT_STATUS_FIELD, dbmodels.EXPORT_STATUS_COMPLETE) + record.Set(dbmodels.EXPORT_PROGRESS_FIELD, 100) + record.Set(dbmodels.EXPORT_TABLES_DONE_FIELD, len(files)) + record.Set(dbmodels.EXPORT_FILENAME_FIELD, filename) + record.Set(dbmodels.EXPORT_SIZE_FIELD, stat.Size()) + record.Set(dbmodels.EXPORT_CURRENT_TABLE_FIELD, "") + if missing > 0 { + record.Set(dbmodels.EXPORT_ERROR_FIELD, fmt.Sprintf("%d Datei(en) fehlen im Speicher.", missing)) + } else { + record.Set(dbmodels.EXPORT_ERROR_FIELD, "") + } + if err := app.Save(record); err != nil { + return err + } + + return nil +} + +func collectFileEntries(app core.App) ([]fileEntry, error) { + collections, err := app.FindAllCollections() + if err != nil { + return nil, err + } + + entries := make([]fileEntry, 0) + seen := make(map[string]struct{}) + + for _, collection := range collections { + if collection == nil || collection.IsView() { + continue + } + if strings.HasPrefix(collection.Name, "_") { + continue + } + + fileFields := make([]string, 0) + for _, field := range collection.Fields { + if field.Type() == core.FieldTypeFile { + fileFields = append(fileFields, field.GetName()) + } + } + if len(fileFields) == 0 { + continue + } + + records := []*core.Record{} + if err := app.RecordQuery(collection.Name).All(&records); err != nil { + return nil, err + } + + for _, record := range records { + if record == nil { + continue + } + for _, fieldName := range fileFields { + raw := record.GetRaw(fieldName) + for _, filename := range extractFileNames(raw) { + if filename == "" { + continue + } + key := collection.Id + "|" + record.Id + "|" + filename + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + entries = append(entries, fileEntry{ + CollectionName: collection.Name, + CollectionId: collection.Id, + RecordId: record.Id, + FieldName: fieldName, + Filename: filename, + }) + } + } + } + } + + return entries, nil +} + +func extractFileNames(raw any) []string { + switch value := raw.(type) { + case nil: + return nil + case string: + v := strings.TrimSpace(value) + if v == "" { + return nil + } + if strings.HasPrefix(v, "[") { + var list []string + if err := json.Unmarshal([]byte(v), &list); err == nil { + return list + } + } + return []string{v} + case []string: + return value + case []any: + out := make([]string, 0, len(value)) + for _, item := range value { + if s, ok := item.(string); ok { + out = append(out, s) + } + } + return out + case []byte: + var list []string + if err := json.Unmarshal(value, &list); err == nil { + return list + } + } + return nil +} + +func addFileToZip(app core.App, zipWriter *zip.Writer, entry fileEntry) error { + root := filepath.Join(app.DataDir(), "storage") + sourcePath := filepath.Join(root, entry.CollectionId, entry.RecordId, entry.Filename) + + reader, err := os.Open(sourcePath) + if err != nil { + return err + } + defer reader.Close() + + zipPath := entry.CollectionName + "/" + entry.RecordId + "/" + entry.Filename + zipEntry, err := zipWriter.Create(zipPath) + if err != nil { + return err + } + + _, err = io.Copy(zipEntry, reader) + return err +} + +func shouldUpdateProgress(done, total int, lastSave time.Time) bool { + if total == 0 { + return true + } + if done == 0 || done >= total { + return true + } + if done%200 == 0 { + return true + } + return time.Since(lastSave) > 2*time.Second +} diff --git a/migrations/1769000000_exports.go b/migrations/1769000000_exports.go new file mode 100644 index 0000000..8f6264b --- /dev/null +++ b/migrations/1769000000_exports.go @@ -0,0 +1,99 @@ +package migrations + +import ( + "strings" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + usersCollection, err := app.FindCollectionByNameOrId(dbmodels.USERS_TABLE) + if err != nil { + app.Logger().Error("Failed to find users collection for exports migration", "error", err) + return err + } + + collection := core.NewBaseCollection(dbmodels.EXPORTS_TABLE) + fields := exportsFields(usersCollection.Id) + dbmodels.SetCreatedUpdatedFields(&fields) + collection.Fields = fields + + dbmodels.AddIndex(collection, dbmodels.EXPORT_STATUS_FIELD, false) + dbmodels.AddIndex(collection, dbmodels.EXPORT_EXPIRES_FIELD, false) + dbmodels.AddIndex(collection, dbmodels.EXPORT_CREATED_BY_FIELD, false) + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId(dbmodels.EXPORTS_TABLE) + if err != nil { + if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "no rows in result set") { + return nil + } + app.Logger().Error("Failed to find collection for deletion", "collection", dbmodels.EXPORTS_TABLE, "error", err) + return err + } + + return app.Delete(collection) + }) +} + +func exportsFields(usersCollectionId string) core.FieldsList { + fields := core.NewFieldsList( + &core.TextField{ + Name: dbmodels.EXPORT_NAME_FIELD, + Required: true, + Presentable: true, + }, + &core.TextField{ + Name: dbmodels.EXPORT_FILENAME_FIELD, + Presentable: true, + }, + &core.SelectField{ + Name: dbmodels.EXPORT_STATUS_FIELD, + Required: true, + Presentable: true, + MaxSelect: 1, + Values: dbmodels.EXPORT_STATUS_VALUES, + }, + &core.NumberField{ + Name: dbmodels.EXPORT_PROGRESS_FIELD, + Presentable: true, + }, + &core.NumberField{ + Name: dbmodels.EXPORT_TABLES_TOTAL_FIELD, + Presentable: true, + }, + &core.NumberField{ + Name: dbmodels.EXPORT_TABLES_DONE_FIELD, + Presentable: true, + }, + &core.NumberField{ + Name: dbmodels.EXPORT_SIZE_FIELD, + Presentable: true, + }, + &core.TextField{ + Name: dbmodels.EXPORT_CURRENT_TABLE_FIELD, + Presentable: true, + }, + &core.TextField{ + Name: dbmodels.EXPORT_ERROR_FIELD, + Presentable: true, + }, + &core.DateField{ + Name: dbmodels.EXPORT_EXPIRES_FIELD, + Presentable: true, + }, + &core.RelationField{ + Name: dbmodels.EXPORT_CREATED_BY_FIELD, + CollectionId: usersCollectionId, + MaxSelect: 1, + CascadeDelete: true, + Presentable: true, + }, + ) + + return fields +} diff --git a/migrations/1769000001_exports_type.go b/migrations/1769000001_exports_type.go new file mode 100644 index 0000000..8f791b4 --- /dev/null +++ b/migrations/1769000001_exports_type.go @@ -0,0 +1,41 @@ +package migrations + +import ( + "strings" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId(dbmodels.EXPORTS_TABLE) + if err != nil { + app.Logger().Error("Failed to find exports collection for type migration", "error", err) + return err + } + + field := &core.SelectField{ + Name: dbmodels.EXPORT_TYPE_FIELD, + Presentable: true, + MaxSelect: 1, + Values: dbmodels.EXPORT_TYPE_VALUES, + } + + collection.Fields.Add(field) + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId(dbmodels.EXPORTS_TABLE) + if err != nil { + if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "no rows in result set") { + return nil + } + app.Logger().Error("Failed to find exports collection for type rollback", "error", err) + return err + } + + collection.Fields.RemoveByName(dbmodels.EXPORT_TYPE_FIELD) + return app.Save(collection) + }) +} diff --git a/templating/request.go b/templating/request.go index fcbe374..0e65c80 100644 --- a/templating/request.go +++ b/templating/request.go @@ -2,6 +2,7 @@ package templating import ( "fmt" + "strings" "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" "github.com/pocketbase/pocketbase/core" @@ -87,7 +88,11 @@ func (r *Request) IsEditor() bool { } func (r *Request) CheckCSRF(target string) error { - if r.Session() == nil || target == "" || r.Session().Token != target { + target = strings.TrimSpace(target) + if r.Session() == nil || target == "" { + return fmt.Errorf("CSRF-Token nicht vorhanden oder ungültig") + } + if r.Session().Token != target && r.Session().CSRF != target { return fmt.Errorf("CSRF-Token nicht vorhanden oder ungültig") } return nil diff --git a/views/assets/scripts.js b/views/assets/scripts.js index 5a0b61e..dd31181 100644 --- a/views/assets/scripts.js +++ b/views/assets/scripts.js @@ -1,15 +1,22 @@ -var Pa = "2.1.16"; -const Bt = "[data-trix-attachment]", Sn = { preview: { presentation: "gallery", caption: { name: !0, size: !0 } }, file: { caption: { size: !0 } } }, X = { default: { tagName: "div", parse: !1 }, quote: { tagName: "blockquote", nestable: !0 }, heading1: { tagName: "h1", terminal: !0, breakOnReturn: !0, group: !1 }, code: { tagName: "pre", terminal: !0, htmlAttributes: ["language"], text: { plaintext: !0 } }, bulletList: { tagName: "ul", parse: !1 }, bullet: { tagName: "li", listAttribute: "bulletList", group: !1, nestable: !0, test(s) { - return ss(s.parentNode) === X[this.listAttribute].tagName; +var Za = Object.defineProperty; +var fs = (s) => { + throw TypeError(s); +}; +var to = (s, t, e) => t in s ? Za(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e; +var Xt = (s, t, e) => to(s, typeof t != "symbol" ? t + "" : t, e), Oi = (s, t, e) => t.has(s) || fs("Cannot " + e); +var Bi = (s, t, e) => (Oi(s, t, "read from private field"), e ? e.call(s) : t.get(s)), he = (s, t, e) => t.has(s) ? fs("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(s) : t.set(s, e), Ve = (s, t, e, i) => (Oi(s, t, "write to private field"), i ? i.call(s, e) : t.set(s, e), e), je = (s, t, e) => (Oi(s, t, "access private method"), e); +var eo = "2.1.16"; +const Mt = "[data-trix-attachment]", Pn = { preview: { presentation: "gallery", caption: { name: !0, size: !0 } }, file: { caption: { size: !0 } } }, X = { default: { tagName: "div", parse: !1 }, quote: { tagName: "blockquote", nestable: !0 }, heading1: { tagName: "h1", terminal: !0, breakOnReturn: !0, group: !1 }, code: { tagName: "pre", terminal: !0, htmlAttributes: ["language"], text: { plaintext: !0 } }, bulletList: { tagName: "ul", parse: !1 }, bullet: { tagName: "li", listAttribute: "bulletList", group: !1, nestable: !0, test(s) { + return bs(s.parentNode) === X[this.listAttribute].tagName; } }, numberList: { tagName: "ol", parse: !1 }, number: { tagName: "li", listAttribute: "numberList", group: !1, nestable: !0, test(s) { - return ss(s.parentNode) === X[this.listAttribute].tagName; -} }, attachmentGallery: { tagName: "div", exclusive: !0, terminal: !0, parse: !1, group: !1 } }, ss = (s) => { + return bs(s.parentNode) === X[this.listAttribute].tagName; +} }, attachmentGallery: { tagName: "div", exclusive: !0, terminal: !0, parse: !1, group: !1 } }, bs = (s) => { var t; return s == null || (t = s.tagName) === null || t === void 0 ? void 0 : t.toLowerCase(); -}, rs = navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i), Si = rs && parseInt(rs[1]); -var Te = { composesExistingText: /Android.*Chrome/.test(navigator.userAgent), recentAndroid: Si && Si > 12, samsungAndroid: Si && navigator.userAgent.match(/Android.*SM-/), forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent), supportsInputEvents: typeof InputEvent < "u" && ["data", "getTargetRanges", "inputType"].every(((s) => s in InputEvent.prototype)) }, Mr = { ADD_ATTR: ["language"], SAFE_FOR_XML: !1, RETURN_DOM: !0 }, v = { attachFiles: "Attach Files", bold: "Bold", bullets: "Bullets", byte: "Byte", bytes: "Bytes", captionPlaceholder: "Add a caption…", code: "Code", heading1: "Heading", indent: "Increase Level", italic: "Italic", link: "Link", numbers: "Numbers", outdent: "Decrease Level", quote: "Quote", redo: "Redo", remove: "Remove", strike: "Strikethrough", undo: "Undo", unlink: "Unlink", url: "URL", urlPlaceholder: "Enter a URL…", GB: "GB", KB: "KB", MB: "MB", PB: "PB", TB: "TB" }; -const Fa = [v.bytes, v.KB, v.MB, v.GB, v.TB, v.PB]; -var Nr = { prefix: "IEC", precision: 2, formatter(s) { +}, _s = navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i), Mi = _s && parseInt(_s[1]); +var Oe = { composesExistingText: /Android.*Chrome/.test(navigator.userAgent), recentAndroid: Mi && Mi > 12, samsungAndroid: Mi && navigator.userAgent.match(/Android.*SM-/), forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent), supportsInputEvents: typeof InputEvent < "u" && ["data", "getTargetRanges", "inputType"].every((s) => s in InputEvent.prototype) }, Gr = { ADD_ATTR: ["language"], SAFE_FOR_XML: !1, RETURN_DOM: !0 }, v = { attachFiles: "Attach Files", bold: "Bold", bullets: "Bullets", byte: "Byte", bytes: "Bytes", captionPlaceholder: "Add a caption…", code: "Code", heading1: "Heading", indent: "Increase Level", italic: "Italic", link: "Link", numbers: "Numbers", outdent: "Decrease Level", quote: "Quote", redo: "Redo", remove: "Remove", strike: "Strikethrough", undo: "Undo", unlink: "Unlink", url: "URL", urlPlaceholder: "Enter a URL…", GB: "GB", KB: "KB", MB: "MB", PB: "PB", TB: "TB" }; +const io = [v.bytes, v.KB, v.MB, v.GB, v.TB, v.PB]; +var Jr = { prefix: "IEC", precision: 2, formatter(s) { switch (s) { case 0: return "0 ".concat(v.bytes); @@ -19,34 +26,34 @@ var Nr = { prefix: "IEC", precision: 2, formatter(s) { let t; this.prefix === "SI" ? t = 1e3 : this.prefix === "IEC" && (t = 1024); const e = Math.floor(Math.log(s) / Math.log(t)), i = (s / Math.pow(t, e)).toFixed(this.precision).replace(/0*$/, "").replace(/\.$/, ""); - return "".concat(i, " ").concat(Fa[e]); + return "".concat(i, " ").concat(io[e]); } } }; -const ni = "\uFEFF", St = " ", Pr = function(s) { +const ui = "\uFEFF", St = " ", Yr = function(s) { for (const t in s) { const e = s[t]; this[t] = e; } return this; -}, Ln = document.documentElement, Ha = Ln.matches, B = function(s) { +}, Fn = document.documentElement, no = Fn.matches, B = function(s) { let { onElement: t, matchingSelector: e, withCallback: i, inPhase: n, preventDefault: r, times: a } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; - const o = t || Ln, l = e, c = n === "capturing", u = function(m) { + const o = t || Fn, l = e, c = n === "capturing", u = function(m) { a != null && --a == 0 && u.destroy(); const p = Lt(m.target, { matchingSelector: l }); - p != null && (i?.call(p, m, p), r && m.preventDefault()); + p != null && (i == null || i.call(p, m, p), r && m.preventDefault()); }; return u.destroy = () => o.removeEventListener(s, u, c), o.addEventListener(s, u, c), u; -}, Fr = function(s) { +}, Xr = function(s) { let { bubbles: t, cancelable: e, attributes: i } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; t = t !== !1, e = e !== !1; const n = document.createEvent("Events"); - return n.initEvent(s, t, e), i != null && Pr.call(n, i), n; -}, be = function(s) { + return n.initEvent(s, t, e), i != null && Yr.call(n, i), n; +}, Ae = function(s) { let { onElement: t, bubbles: e, cancelable: i, attributes: n } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; - const r = t ?? Ln, a = Fr(s, { bubbles: e, cancelable: i, attributes: n }); + const r = t ?? Fn, a = Xr(s, { bubbles: e, cancelable: i, attributes: n }); return r.dispatchEvent(a); -}, Hr = function(s, t) { - if (s?.nodeType === 1) return Ha.call(s, t); +}, Qr = function(s, t) { + if ((s == null ? void 0 : s.nodeType) === 1) return no.call(s, t); }, Lt = function(s) { let { matchingSelector: t, untilNode: e } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; for (; s && s.nodeType !== Node.ELEMENT_NODE; ) s = s.parentNode; @@ -54,16 +61,16 @@ const ni = "\uFEFF", St = " ", Pr = function(s) { if (t == null) return s; if (s.closest && e == null) return s.closest(t); for (; s && s !== e; ) { - if (Hr(s, t)) return s; + if (Qr(s, t)) return s; s = s.parentNode; } } -}, Cn = (s) => document.activeElement !== s && Ot(s, document.activeElement), Ot = function(s, t) { +}, Hn = (s) => document.activeElement !== s && Ot(s, document.activeElement), Ot = function(s, t) { if (s && t) for (; t; ) { if (t === s) return !0; t = t.parentNode; } -}, Li = function(s) { +}, Ni = function(s) { var t; if ((t = s) === null || t === void 0 || !t.parentNode) return; let e = 0; @@ -72,7 +79,7 @@ const ni = "\uFEFF", St = " ", Pr = function(s) { }, Ct = (s) => { var t; return s == null || (t = s.parentNode) === null || t === void 0 ? void 0 : t.removeChild(s); -}, Qe = function(s) { +}, ri = function(s) { let { onlyNodesOfType: t, usingFilter: e, expandEntityReferences: i } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; const n = (() => { switch (t) { @@ -97,45 +104,45 @@ const ni = "\uFEFF", St = " ", Pr = function(s) { if (i.editable != null && (i.attributes == null && (i.attributes = {}), i.attributes.contenteditable = i.editable), i.attributes) for (t in i.attributes) e = i.attributes[t], n.setAttribute(t, e); if (i.style) for (t in i.style) e = i.style[t], n.style[t] = e; if (i.data) for (t in i.data) e = i.data[t], n.dataset[t] = e; - return i.className && i.className.split(" ").forEach(((r) => { + return i.className && i.className.split(" ").forEach((r) => { n.classList.add(r); - })), i.textContent && (n.textContent = i.textContent), i.childNodes && [].concat(i.childNodes).forEach(((r) => { + }), i.textContent && (n.textContent = i.textContent), i.childNodes && [].concat(i.childNodes).forEach((r) => { n.appendChild(r); - })), n; + }), n; }; -let le; -const _e = function() { - if (le != null) return le; - le = []; +let ue; +const Ee = function() { + if (ue != null) return ue; + ue = []; for (const s in X) { const t = X[s]; - t.tagName && le.push(t.tagName); + t.tagName && ue.push(t.tagName); } - return le; -}, Ci = (s) => Zt(s?.firstChild), as = function(s) { + return ue; +}, Pi = (s) => ee(s == null ? void 0 : s.firstChild), vs = function(s) { let { strict: t } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : { strict: !0 }; - return t ? Zt(s) : Zt(s) || !Zt(s.firstChild) && (function(e) { - return _e().includes(Y(e)) && !_e().includes(Y(e.firstChild)); - })(s); -}, Zt = (s) => qa(s) && s?.data === "block", qa = (s) => s?.nodeType === Node.COMMENT_NODE, te = function(s) { + return t ? ee(s) : ee(s) || !ee(s.firstChild) && function(e) { + return Ee().includes(Y(e)) && !Ee().includes(Y(e.firstChild)); + }(s); +}, ee = (s) => so(s) && (s == null ? void 0 : s.data) === "block", so = (s) => (s == null ? void 0 : s.nodeType) === Node.COMMENT_NODE, ie = function(s) { let { name: t } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; - if (s) return ve(s) ? s.data === ni ? !t || s.parentNode.dataset.trixCursorTarget === t : void 0 : te(s.firstChild); -}, Mt = (s) => Hr(s, Bt), qr = (s) => ve(s) && s?.data === "", ve = (s) => s?.nodeType === Node.TEXT_NODE, wn = { level2Enabled: !0, getLevel() { - return this.level2Enabled && Te.supportsInputEvents ? 2 : 0; + if (s) return xe(s) ? s.data === ui ? !t || s.parentNode.dataset.trixCursorTarget === t : void 0 : ie(s.firstChild); +}, Nt = (s) => Qr(s, Mt), Zr = (s) => xe(s) && (s == null ? void 0 : s.data) === "", xe = (s) => (s == null ? void 0 : s.nodeType) === Node.TEXT_NODE, qn = { level2Enabled: !0, getLevel() { + return this.level2Enabled && Oe.supportsInputEvents ? 2 : 0; }, pickFiles(s) { const t = x("input", { type: "file", multiple: !0, hidden: !0, id: this.fileInputId }); - t.addEventListener("change", (() => { + t.addEventListener("change", () => { s(t.files), Ct(t); - })), Ct(document.getElementById(this.fileInputId)), document.body.appendChild(t), t.click(); + }), Ct(document.getElementById(this.fileInputId)), document.body.appendChild(t), t.click(); } }; -var Je = { removeBlankTableCells: !1, tableCellSeparator: " | ", tableRowSeparator: ` -` }, Pt = { bold: { tagName: "strong", inheritable: !0, parser(s) { +var ii = { removeBlankTableCells: !1, tableCellSeparator: " | ", tableRowSeparator: ` +` }, Ft = { bold: { tagName: "strong", inheritable: !0, parser(s) { const t = window.getComputedStyle(s); return t.fontWeight === "bold" || t.fontWeight >= 600; } }, italic: { tagName: "em", inheritable: !0, parser: (s) => window.getComputedStyle(s).fontStyle === "italic" }, href: { groupTagName: "a", parser(s) { - const t = "a:not(".concat(Bt, ")"), e = s.closest(t); + const t = "a:not(".concat(Mt, ")"), e = s.closest(t); if (e) return e.getAttribute("href"); -} }, strike: { tagName: "del", inheritable: !0 }, frozen: { style: { backgroundColor: "highlight" } } }, $r = { getDefaultHTML: () => `
`) }; -const cn = { interval: 5e3 }; -var ke = Object.freeze({ __proto__: null, attachments: Sn, blockAttributes: X, browser: Te, css: { attachment: "attachment", attachmentCaption: "attachment__caption", attachmentCaptionEditor: "attachment__caption-editor", attachmentMetadata: "attachment__metadata", attachmentMetadataContainer: "attachment__metadata-container", attachmentName: "attachment__name", attachmentProgress: "attachment__progress", attachmentSize: "attachment__size", attachmentToolbar: "attachment__toolbar", attachmentGallery: "attachment-gallery" }, dompurify: Mr, fileSize: Nr, input: wn, keyNames: { 8: "backspace", 9: "tab", 13: "return", 27: "escape", 37: "left", 39: "right", 46: "delete", 68: "d", 72: "h", 79: "o" }, lang: v, parser: Je, textAttributes: Pt, toolbar: $r, undo: cn }); -class H { +const An = { interval: 5e3 }; +var Be = Object.freeze({ __proto__: null, attachments: Pn, blockAttributes: X, browser: Oe, css: { attachment: "attachment", attachmentCaption: "attachment__caption", attachmentCaptionEditor: "attachment__caption-editor", attachmentMetadata: "attachment__metadata", attachmentMetadataContainer: "attachment__metadata-container", attachmentName: "attachment__name", attachmentProgress: "attachment__progress", attachmentSize: "attachment__size", attachmentToolbar: "attachment__toolbar", attachmentGallery: "attachment-gallery" }, dompurify: Gr, fileSize: Jr, input: qn, keyNames: { 8: "backspace", 9: "tab", 13: "return", 27: "escape", 37: "left", 39: "right", 46: "delete", 68: "d", 72: "h", 79: "o" }, lang: v, parser: ii, textAttributes: Ft, toolbar: ta, undo: An }); +class q { static proxyMethod(t) { - const { name: e, toMethod: i, toProperty: n, optional: r } = $a(t); + const { name: e, toMethod: i, toProperty: n, optional: r } = ro(t); this.prototype[e] = function() { let a, o; var l, c; - return i ? o = r ? (l = this[i]) === null || l === void 0 ? void 0 : l.call(this) : this[i]() : n && (o = this[n]), r ? (a = (c = o) === null || c === void 0 ? void 0 : c[e], a ? os.call(a, o, arguments) : void 0) : (a = o[e], os.call(a, o, arguments)); + return i ? o = r ? (l = this[i]) === null || l === void 0 ? void 0 : l.call(this) : this[i]() : n && (o = this[n]), r ? (a = (c = o) === null || c === void 0 ? void 0 : c[e], a ? ys.call(a, o, arguments) : void 0) : (a = o[e], ys.call(a, o, arguments)); }; } } -const $a = function(s) { - const t = s.match(Ua); +const ro = function(s) { + const t = s.match(ao); if (!t) throw new Error("can't parse @proxyMethod expression: ".concat(s)); const e = { name: t[4] }; return t[2] != null ? e.toMethod = t[1] : e.toProperty = t[1], t[3] != null && (e.optional = !0), e; -}, { apply: os } = Function.prototype, Ua = new RegExp("^(.+?)(\\(\\))?(\\?)?\\.(.+?)$"); -var wi, Ti, ki; -class Le extends H { +}, { apply: ys } = Function.prototype, ao = new RegExp("^(.+?)(\\(\\))?(\\?)?\\.(.+?)$"); +var Fi, Hi, qi; +class ke extends q { static box() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : ""; - return t instanceof this ? t : this.fromUCS2String(t?.toString()); + return t instanceof this ? t : this.fromUCS2String(t == null ? void 0 : t.toString()); } static fromUCS2String(t) { - return new this(t, hn(t)); + return new this(t, En(t)); } static fromCodepoints(t) { - return new this(un(t), t); + return new this(xn(t), t); } constructor(t, e) { super(...arguments), this.ucs2String = t, this.codepoints = e, this.length = this.codepoints.length, this.ucs2Length = this.ucs2String.length; } offsetToUCS2Offset(t) { - return un(this.codepoints.slice(0, Math.max(0, t))).length; + return xn(this.codepoints.slice(0, Math.max(0, t))).length; } offsetFromUCS2Offset(t) { - return hn(this.ucs2String.slice(0, Math.max(0, t))).length; + return En(this.ucs2String.slice(0, Math.max(0, t))).length; } slice() { return this.constructor.fromCodepoints(this.codepoints.slice(...arguments)); @@ -234,9 +241,9 @@ class Le extends H { return this.ucs2String; } } -const Va = ((wi = Array.from) === null || wi === void 0 ? void 0 : wi.call(Array, "👼").length) === 1, ja = ((Ti = " ".codePointAt) === null || Ti === void 0 ? void 0 : Ti.call(" ", 0)) != null, Wa = ((ki = String.fromCodePoint) === null || ki === void 0 ? void 0 : ki.call(String, 32, 128124)) === " 👼"; -let hn, un; -hn = Va && ja ? (s) => Array.from(s).map(((t) => t.codePointAt(0))) : function(s) { +const oo = ((Fi = Array.from) === null || Fi === void 0 ? void 0 : Fi.call(Array, "👼").length) === 1, lo = ((Hi = " ".codePointAt) === null || Hi === void 0 ? void 0 : Hi.call(" ", 0)) != null, co = ((qi = String.fromCodePoint) === null || qi === void 0 ? void 0 : qi.call(String, 32, 128124)) === " 👼"; +let En, xn; +En = oo && lo ? (s) => Array.from(s).map((t) => t.codePointAt(0)) : function(s) { const t = []; let e = 0; const { length: i } = s; @@ -249,25 +256,25 @@ hn = Va && ja ? (s) => Array.from(s).map(((t) => t.codePointAt(0))) : function(s t.push(n); } return t; -}, un = Wa ? (s) => String.fromCodePoint(...Array.from(s || [])) : function(s) { +}, xn = co ? (s) => String.fromCodePoint(...Array.from(s || [])) : function(s) { return (() => { const t = []; - return Array.from(s).forEach(((e) => { + return Array.from(s).forEach((e) => { let i = ""; e > 65535 && (e -= 65536, i += String.fromCharCode(e >>> 10 & 1023 | 55296), e = 56320 | 1023 & e), t.push(i + String.fromCharCode(e)); - })), t; + }), t; })().join(""); }; -let za = 0; -class qt extends H { +let ho = 0; +class $t extends q { static fromJSONString(t) { return this.fromJSON(JSON.parse(t)); } constructor() { - super(...arguments), this.id = ++za; + super(...arguments), this.id = ++ho; } hasSameConstructorAs(t) { - return this.constructor === t?.constructor; + return this.constructor === (t == null ? void 0 : t.constructor); } isEqualTo(t) { return this === t; @@ -286,69 +293,69 @@ class qt extends H { return JSON.stringify(this); } toUTF16String() { - return Le.box(this); + return ke.box(this); } getCacheKey() { return this.id.toString(); } } -const Ft = function() { +const Ht = function() { let s = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : [], t = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : []; if (s.length !== t.length) return !1; for (let e = 0; e < s.length; e++) if (s[e] !== t[e]) return !1; return !0; -}, Tn = function(s) { +}, $n = function(s) { const t = s.slice(0); for (var e = arguments.length, i = new Array(e > 1 ? e - 1 : 0), n = 1; n < e; n++) i[n - 1] = arguments[n]; return t.splice(...i), t; -}, Ka = /[\u05BE\u05C0\u05C3\u05D0-\u05EA\u05F0-\u05F4\u061B\u061F\u0621-\u063A\u0640-\u064A\u066D\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D5\u06E5\u06E6\u200F\u202B\u202E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE72\uFE74\uFE76-\uFEFC]/, Ga = (function() { +}, uo = /[\u05BE\u05C0\u05C3\u05D0-\u05EA\u05F0-\u05F4\u061B\u061F\u0621-\u063A\u0640-\u064A\u066D\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D5\u06E5\u06E6\u200F\u202B\u202E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE72\uFE74\uFE76-\uFEFC]/, mo = function() { const s = x("input", { dir: "auto", name: "x", dirName: "x.dir" }), t = x("textarea", { dir: "auto", name: "y", dirName: "y.dir" }), e = x("form"); e.appendChild(s), e.appendChild(t); - const i = (function() { + const i = function() { try { return new FormData(e).has(t.dirName); } catch { return !1; } - })(), n = (function() { + }(), n = function() { try { return s.matches(":dir(ltr),:dir(rtl)"); } catch { return !1; } - })(); + }(); return i ? function(r) { return t.value = r, new FormData(e).get(t.dirName); } : n ? function(r) { return s.value = r, s.matches(":dir(rtl)") ? "rtl" : "ltr"; } : function(r) { const a = r.trim().charAt(0); - return Ka.test(a) ? "rtl" : "ltr"; + return uo.test(a) ? "rtl" : "ltr"; }; -})(); -let Ii = null, Ri = null, Di = null, Fe = null; -const mn = () => (Ii || (Ii = Ya().concat(Ja())), Ii), P = (s) => X[s], Ja = () => (Ri || (Ri = Object.keys(X)), Ri), gn = (s) => Pt[s], Ya = () => (Di || (Di = Object.keys(Pt)), Di), Ur = function(s, t) { - Xa(s).textContent = t.replace(/%t/g, s); -}, Xa = function(s) { +}(); +let $i = null, Ui = null, Vi = null, We = null; +const Sn = () => ($i || ($i = po().concat(go())), $i), F = (s) => X[s], go = () => (Ui || (Ui = Object.keys(X)), Ui), Ln = (s) => Ft[s], po = () => (Vi || (Vi = Object.keys(Ft)), Vi), ea = function(s, t) { + fo(s).textContent = t.replace(/%t/g, s); +}, fo = function(s) { const t = document.createElement("style"); t.setAttribute("type", "text/css"), t.setAttribute("data-tag-name", s.toLowerCase()); - const e = Qa(); + const e = bo(); return e && t.setAttribute("nonce", e), document.head.insertBefore(t, document.head.firstChild), t; -}, Qa = function() { - const s = ls("trix-csp-nonce") || ls("csp-nonce"); +}, bo = function() { + const s = As("trix-csp-nonce") || As("csp-nonce"); if (s) { const { nonce: t, content: e } = s; return t == "" ? e : t; } -}, ls = (s) => document.head.querySelector("meta[name=".concat(s, "]")), ds = { "application/x-trix-feature-detection": "test" }, Vr = function(s) { +}, As = (s) => document.head.querySelector("meta[name=".concat(s, "]")), Es = { "application/x-trix-feature-detection": "test" }, ia = function(s) { const t = s.getData("text/plain"), e = s.getData("text/html"); - if (!t || !e) return t?.length; + if (!t || !e) return t == null ? void 0 : t.length; { const { body: i } = new DOMParser().parseFromString(e, "text/html"); if (i.textContent === t) return !i.querySelector("*"); } -}, jr = /Mac|^iP/.test(navigator.platform) ? (s) => s.metaKey : (s) => s.ctrlKey, kn = (s) => setTimeout(s, 1), Wr = function() { +}, na = /Mac|^iP/.test(navigator.platform) ? (s) => s.metaKey : (s) => s.ctrlKey, Un = (s) => setTimeout(s, 1), sa = function() { let s = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; const t = {}; for (const e in s) { @@ -356,28 +363,28 @@ const mn = () => (Ii || (Ii = Ya().concat(Ja())), Ii), P = (s) => X[s], Ja = () t[e] = i; } return t; -}, ne = function() { +}, re = function() { let s = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, t = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; if (Object.keys(s).length !== Object.keys(t).length) return !1; for (const e in s) if (s[e] !== t[e]) return !1; return !0; }, k = function(s) { - if (s != null) return Array.isArray(s) || (s = [s, s]), [cs(s[0]), cs(s[1] != null ? s[1] : s[0])]; -}, _t = function(s) { + if (s != null) return Array.isArray(s) || (s = [s, s]), [xs(s[0]), xs(s[1] != null ? s[1] : s[0])]; +}, vt = function(s) { if (s == null) return; const [t, e] = k(s); - return pn(t, e); -}, Ze = function(s, t) { + return Cn(t, e); +}, ai = function(s, t) { if (s == null || t == null) return; const [e, i] = k(s), [n, r] = k(t); - return pn(e, n) && pn(i, r); -}, cs = function(s) { - return typeof s == "number" ? s : Wr(s); -}, pn = function(s, t) { - return typeof s == "number" ? s === t : ne(s, t); + return Cn(e, n) && Cn(i, r); +}, xs = function(s) { + return typeof s == "number" ? s : sa(s); +}, Cn = function(s, t) { + return typeof s == "number" ? s === t : re(s, t); }; -class zr extends H { +class ra extends q { constructor() { super(...arguments), this.update = this.update.bind(this), this.selectionManagers = []; } @@ -391,10 +398,10 @@ class zr extends H { if (!this.selectionManagers.includes(t)) return this.selectionManagers.push(t), this.start(); } unregisterSelectionManager(t) { - if (this.selectionManagers = this.selectionManagers.filter(((e) => e !== t)), this.selectionManagers.length === 0) return this.stop(); + if (this.selectionManagers = this.selectionManagers.filter((e) => e !== t), this.selectionManagers.length === 0) return this.stop(); } notifySelectionManagersOfSelectionChange() { - return this.selectionManagers.map(((t) => t.selectionDidChange())); + return this.selectionManagers.map((t) => t.selectionDidChange()); } update() { this.notifySelectionManagersOfSelectionChange(); @@ -403,52 +410,52 @@ class zr extends H { this.update(); } } -const Ht = new zr(), Kr = function() { +const qt = new ra(), aa = function() { const s = window.getSelection(); if (s.rangeCount > 0) return s; -}, ye = function() { +}, Se = function() { var s; - const t = (s = Kr()) === null || s === void 0 ? void 0 : s.getRangeAt(0); - if (t && !Za(t)) return t; -}, Gr = function(s) { + const t = (s = aa()) === null || s === void 0 ? void 0 : s.getRangeAt(0); + if (t && !_o(t)) return t; +}, oa = function(s) { const t = window.getSelection(); - return t.removeAllRanges(), t.addRange(s), Ht.update(); -}, Za = (s) => hs(s.startContainer) || hs(s.endContainer), hs = (s) => !Object.getPrototypeOf(s), fe = (s) => s.replace(new RegExp("".concat(ni), "g"), "").replace(new RegExp("".concat(St), "g"), " "), In = new RegExp("[^\\S".concat(St, "]")), Rn = (s) => s.replace(new RegExp("".concat(In.source), "g"), " ").replace(/\ {2,}/g, " "), us = function(s, t) { + return t.removeAllRanges(), t.addRange(s), qt.update(); +}, _o = (s) => Ss(s.startContainer) || Ss(s.endContainer), Ss = (s) => !Object.getPrototypeOf(s), ye = (s) => s.replace(new RegExp("".concat(ui), "g"), "").replace(new RegExp("".concat(St), "g"), " "), Vn = new RegExp("[^\\S".concat(St, "]")), jn = (s) => s.replace(new RegExp("".concat(Vn.source), "g"), " ").replace(/\ {2,}/g, " "), Ls = function(s, t) { if (s.isEqualTo(t)) return ["", ""]; - const e = Oi(s, t), { length: i } = e.utf16String; + const e = ji(s, t), { length: i } = e.utf16String; let n; if (i) { const { offset: r } = e, a = s.codepoints.slice(0, r).concat(s.codepoints.slice(r + i)); - n = Oi(t, Le.fromCodepoints(a)); - } else n = Oi(t, s); + n = ji(t, ke.fromCodepoints(a)); + } else n = ji(t, s); return [e.utf16String.toString(), n.utf16String.toString()]; -}, Oi = function(s, t) { +}, ji = function(s, t) { let e = 0, i = s.length, n = t.length; for (; e < i && s.charAt(e).isEqualTo(t.charAt(e)); ) e++; for (; i > e + 1 && s.charAt(i - 1).isEqualTo(t.charAt(n - 1)); ) i--, n--; return { utf16String: s.slice(e, i), offset: e }; }; -class et extends qt { +class et extends $t { static fromCommonAttributesOfObjects() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : []; if (!t.length) return new this(); - let e = de(t[0]), i = e.getKeys(); - return t.slice(1).forEach(((n) => { - i = e.getKeysCommonToHash(de(n)), e = e.slice(i); - })), e; + let e = me(t[0]), i = e.getKeys(); + return t.slice(1).forEach((n) => { + i = e.getKeysCommonToHash(me(n)), e = e.slice(i); + }), e; } static box(t) { - return de(t); + return me(t); } constructor() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; - super(...arguments), this.values = Ye(t); + super(...arguments), this.values = ni(t); } add(t, e) { - return this.merge(to(t, e)); + return this.merge(vo(t, e)); } remove(t) { - return new et(Ye(this.values, t)); + return new et(ni(this.values, t)); } get(t) { return this.values[t]; @@ -457,22 +464,22 @@ class et extends qt { return t in this.values; } merge(t) { - return new et(eo(this.values, io(t))); + return new et(yo(this.values, Ao(t))); } slice(t) { const e = {}; - return Array.from(t).forEach(((i) => { + return Array.from(t).forEach((i) => { this.has(i) && (e[i] = this.values[i]); - })), new et(e); + }), new et(e); } getKeys() { return Object.keys(this.values); } getKeysCommonToHash(t) { - return t = de(t), this.getKeys().filter(((e) => this.values[e] === t.values[e])); + return t = me(t), this.getKeys().filter((e) => this.values[e] === t.values[e]); } isEqualTo(t) { - return Ft(this.toArray(), de(t).toArray()); + return Ht(this.toArray(), me(t).toArray()); } isEmpty() { return this.getKeys().length === 0; @@ -489,7 +496,7 @@ class et extends qt { return this.array; } toObject() { - return Ye(this.values); + return ni(this.values); } toJSON() { return this.toObject(); @@ -498,32 +505,32 @@ class et extends qt { return { values: JSON.stringify(this.values) }; } } -const to = function(s, t) { +const vo = function(s, t) { const e = {}; return e[s] = t, e; -}, eo = function(s, t) { - const e = Ye(s); +}, yo = function(s, t) { + const e = ni(s); for (const i in t) { const n = t[i]; e[i] = n; } return e; -}, Ye = function(s, t) { +}, ni = function(s, t) { const e = {}; - return Object.keys(s).sort().forEach(((i) => { + return Object.keys(s).sort().forEach((i) => { i !== t && (e[i] = s[i]); - })), e; -}, de = function(s) { + }), e; +}, me = function(s) { return s instanceof et ? s : new et(s); -}, io = function(s) { +}, Ao = function(s) { return s instanceof et ? s.values : s; }; -class Dn { +class Wn { static groupObjects() { let t, e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : [], { depth: i, asTree: n } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; n && i == null && (i = 0); const r = []; - return Array.from(e).forEach(((a) => { + return Array.from(e).forEach((a) => { var o; if (t) { var l, c, u; @@ -531,7 +538,7 @@ class Dn { r.push(new this(t, { depth: i, asTree: n })), t = null; } (o = a.canBeGrouped) !== null && o !== void 0 && o.call(a, i) ? t = [a] : r.push(a); - })), t && r.push(new this(t, { depth: i, asTree: n })), r; + }), t && r.push(new this(t, { depth: i, asTree: n })), r; } constructor() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : [], { depth: e, asTree: i } = arguments.length > 1 ? arguments[1] : void 0; @@ -545,45 +552,45 @@ class Dn { } getCacheKey() { const t = ["objectGroup"]; - return Array.from(this.getObjects()).forEach(((e) => { + return Array.from(this.getObjects()).forEach((e) => { t.push(e.getCacheKey()); - })), t.join("/"); + }), t.join("/"); } } -class no extends H { +class Eo extends q { constructor() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : []; - super(...arguments), this.objects = {}, Array.from(t).forEach(((e) => { + super(...arguments), this.objects = {}, Array.from(t).forEach((e) => { const i = JSON.stringify(e); this.objects[i] == null && (this.objects[i] = e); - })); + }); } find(t) { const e = JSON.stringify(t); return this.objects[e]; } } -class so { +class xo { constructor(t) { this.reset(t); } add(t) { - const e = ms(t); + const e = Cs(t); this.elements[e] = t; } remove(t) { - const e = ms(t), i = this.elements[e]; + const e = Cs(t), i = this.elements[e]; if (i) return delete this.elements[e], i; } reset() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : []; - return this.elements = {}, Array.from(t).forEach(((e) => { + return this.elements = {}, Array.from(t).forEach((e) => { this.add(e); - })), t; + }), t; } } -const ms = (s) => s.dataset.trixStoreKey; -class ti extends H { +const Cs = (s) => s.dataset.trixStoreKey; +class oi extends q { isPerforming() { return this.performing === !0; } @@ -597,9 +604,9 @@ class ti extends H { return this.performed && !this.succeeded; } getPromise() { - return this.promise || (this.promise = new Promise(((t, e) => (this.performing = !0, this.perform(((i, n) => { + return this.promise || (this.promise = new Promise((t, e) => (this.performing = !0, this.perform((i, n) => { this.succeeded = i, this.performing = !1, this.performed = !0, this.succeeded ? t(n) : e(n); - })))))), this.promise; + })))), this.promise; } perform(t) { return t(!1); @@ -609,14 +616,14 @@ class ti extends H { (t = this.promise) === null || t === void 0 || (e = t.cancel) === null || e === void 0 || e.call(t), this.promise = null, this.performing = null, this.performed = null, this.succeeded = null; } } -ti.proxyMethod("getPromise().then"), ti.proxyMethod("getPromise().catch"); -class $t extends H { +oi.proxyMethod("getPromise().then"), oi.proxyMethod("getPromise().catch"); +class Ut extends q { constructor(t) { let e = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; super(...arguments), this.object = t, this.options = e, this.childViews = [], this.rootView = this; } getNodes() { - return this.nodes || (this.nodes = this.createNodes()), this.nodes.map(((t) => t.cloneNode(!0))); + return this.nodes || (this.nodes = this.createNodes()), this.nodes.map((t) => t.cloneNode(!0)); } invalidate() { var t; @@ -632,7 +639,7 @@ class $t extends H { } createChildView(t, e) { let i = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; - e instanceof Dn && (i.viewClass = t, t = ro); + e instanceof Wn && (i.viewClass = t, t = So); const n = new t(e, i); return this.recordChildView(n); } @@ -641,15 +648,15 @@ class $t extends H { } getAllChildViews() { let t = []; - return this.childViews.forEach(((e) => { + return this.childViews.forEach((e) => { t.push(e), t = t.concat(e.getAllChildViews()); - })), t; + }), t; } findElement() { return this.findElementForObject(this.object); } findElementForObject(t) { - const e = t?.id; + const e = t == null ? void 0 : t.id; if (e) return this.rootView.element.querySelector("[data-trix-id='".concat(e, "']")); } findViewForObject(t) { @@ -678,246 +685,247 @@ class $t extends H { garbageCollectCachedViews() { const t = this.getViewCache(); if (t) { - const e = this.getAllChildViews().concat(this).map(((i) => i.object.getCacheKey())); + const e = this.getAllChildViews().concat(this).map((i) => i.object.getCacheKey()); for (const i in t) e.includes(i) || delete t[i]; } } } -class ro extends $t { +class So extends Ut { constructor() { super(...arguments), this.objectGroup = this.object, this.viewClass = this.options.viewClass, delete this.options.viewClass; } getChildViews() { - return this.childViews.length || Array.from(this.objectGroup.getObjects()).forEach(((t) => { + return this.childViews.length || Array.from(this.objectGroup.getObjects()).forEach((t) => { this.findOrCreateCachedChildView(this.viewClass, t, this.options); - })), this.childViews; + }), this.childViews; } createNodes() { const t = this.createContainerElement(); - return this.getChildViews().forEach(((e) => { - Array.from(e.getNodes()).forEach(((i) => { + return this.getChildViews().forEach((e) => { + Array.from(e.getNodes()).forEach((i) => { t.appendChild(i); - })); - })), [t]; + }); + }), [t]; } createContainerElement() { let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.objectGroup.getDepth(); return this.getChildViews()[0].createContainerElement(t); } } -const { entries: Jr, setPrototypeOf: gs, isFrozen: ao, getPrototypeOf: oo, getOwnPropertyDescriptor: lo } = Object; -let { freeze: Q, seal: st, create: Yr } = Object, { apply: fn, construct: bn } = typeof Reflect < "u" && Reflect; +/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */ +const { entries: la, setPrototypeOf: ws, isFrozen: Lo, getPrototypeOf: Co, getOwnPropertyDescriptor: wo } = Object; +let { freeze: Q, seal: st, create: da } = Object, { apply: wn, construct: Tn } = typeof Reflect < "u" && Reflect; Q || (Q = function(s) { return s; }), st || (st = function(s) { return s; -}), fn || (fn = function(s, t) { +}), wn || (wn = function(s, t) { for (var e = arguments.length, i = new Array(e > 2 ? e - 2 : 0), n = 2; n < e; n++) i[n - 2] = arguments[n]; return s.apply(t, i); -}), bn || (bn = function(s) { +}), Tn || (Tn = function(s) { for (var t = arguments.length, e = new Array(t > 1 ? t - 1 : 0), i = 1; i < t; i++) e[i - 1] = arguments[i]; return new s(...e); }); -const He = Z(Array.prototype.forEach), co = Z(Array.prototype.lastIndexOf), ps = Z(Array.prototype.pop), ce = Z(Array.prototype.push), ho = Z(Array.prototype.splice), Xe = Z(String.prototype.toLowerCase), Bi = Z(String.prototype.toString), Mi = Z(String.prototype.match), he = Z(String.prototype.replace), uo = Z(String.prototype.indexOf), mo = Z(String.prototype.trim), lt = Z(Object.prototype.hasOwnProperty), J = Z(RegExp.prototype.test), ue = (fs = TypeError, function() { +const ze = Z(Array.prototype.forEach), To = Z(Array.prototype.lastIndexOf), Ts = Z(Array.prototype.pop), ge = Z(Array.prototype.push), ko = Z(Array.prototype.splice), si = Z(String.prototype.toLowerCase), Wi = Z(String.prototype.toString), zi = Z(String.prototype.match), pe = Z(String.prototype.replace), Io = Z(String.prototype.indexOf), Ro = Z(String.prototype.trim), lt = Z(Object.prototype.hasOwnProperty), J = Z(RegExp.prototype.test), fe = (ks = TypeError, function() { for (var s = arguments.length, t = new Array(s), e = 0; e < s; e++) t[e] = arguments[e]; - return bn(fs, t); + return Tn(ks, t); }); -var fs; +var ks; function Z(s) { return function(t) { t instanceof RegExp && (t.lastIndex = 0); for (var e = arguments.length, i = new Array(e > 1 ? e - 1 : 0), n = 1; n < e; n++) i[n - 1] = arguments[n]; - return fn(s, t, i); + return wn(s, t, i); }; } function L(s, t) { - let e = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : Xe; - gs && gs(s, null); + let e = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : si; + ws && ws(s, null); let i = t.length; for (; i--; ) { let n = t[i]; if (typeof n == "string") { const r = e(n); - r !== n && (ao(t) || (t[i] = r), n = r); + r !== n && (Lo(t) || (t[i] = r), n = r); } s[n] = !0; } return s; } -function go(s) { +function Do(s) { for (let t = 0; t < s.length; t++) lt(s, t) || (s[t] = null); return s; } function ft(s) { - const t = Yr(null); - for (const [e, i] of Jr(s)) - lt(s, e) && (Array.isArray(i) ? t[e] = go(i) : i && typeof i == "object" && i.constructor === Object ? t[e] = ft(i) : t[e] = i); + const t = da(null); + for (const [e, i] of la(s)) + lt(s, e) && (Array.isArray(i) ? t[e] = Do(i) : i && typeof i == "object" && i.constructor === Object ? t[e] = ft(i) : t[e] = i); return t; } -function me(s, t) { +function be(s, t) { for (; s !== null; ) { - const e = lo(s, t); + const e = wo(s, t); if (e) { if (e.get) return Z(e.get); if (typeof e.value == "function") return Z(e.value); } - s = oo(s); + s = Co(s); } return function() { return null; }; } -const bs = Q(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), Ni = Q(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "slot", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), Pi = Q(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), po = Q(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), Fi = Q(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), fo = Q(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), _s = Q(["#text"]), vs = Q(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]), Hi = Q(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), ys = Q(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), qe = Q(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), bo = st(/\{\{[\w\W]*|[\w\W]*\}\}/gm), _o = st(/<%[\w\W]*|[\w\W]*%>/gm), vo = st(/\$\{[\w\W]*/gm), yo = st(/^data-[\-\w.\u00B7-\uFFFF]+$/), Ao = st(/^aria-[\-\w]+$/), Xr = st(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i), Eo = st(/^(?:\w+script|data):/i), xo = st(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g), Qr = st(/^html$/i), So = st(/^[a-z][.\w]*(-[.\w]+)+$/i); -var As = Object.freeze({ __proto__: null, ARIA_ATTR: Ao, ATTR_WHITESPACE: xo, CUSTOM_ELEMENT: So, DATA_ATTR: yo, DOCTYPE_NAME: Qr, ERB_EXPR: _o, IS_ALLOWED_URI: Xr, IS_SCRIPT_OR_DATA: Eo, MUSTACHE_EXPR: bo, TMPLIT_EXPR: vo }); -const Lo = 1, Co = 3, wo = 7, To = 8, ko = 9, Io = function() { +const Is = Q(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), Ki = Q(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "slot", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), Gi = Q(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), Oo = Q(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), Ji = Q(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), Bo = Q(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), Rs = Q(["#text"]), Ds = Q(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]), Yi = Q(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), Os = Q(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), Ke = Q(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), Mo = st(/\{\{[\w\W]*|[\w\W]*\}\}/gm), No = st(/<%[\w\W]*|[\w\W]*%>/gm), Po = st(/\$\{[\w\W]*/gm), Fo = st(/^data-[\-\w.\u00B7-\uFFFF]+$/), Ho = st(/^aria-[\-\w]+$/), ca = st(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i), qo = st(/^(?:\w+script|data):/i), $o = st(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g), ha = st(/^html$/i), Uo = st(/^[a-z][.\w]*(-[.\w]+)+$/i); +var Bs = Object.freeze({ __proto__: null, ARIA_ATTR: Ho, ATTR_WHITESPACE: $o, CUSTOM_ELEMENT: Uo, DATA_ATTR: Fo, DOCTYPE_NAME: ha, ERB_EXPR: No, IS_ALLOWED_URI: ca, IS_SCRIPT_OR_DATA: qo, MUSTACHE_EXPR: Mo, TMPLIT_EXPR: Po }); +const Vo = 1, jo = 3, Wo = 7, zo = 8, Ko = 9, Go = function() { return typeof window > "u" ? null : window; }; -var Ce = (function s() { - let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : Io(); +var Ie = function s() { + let t = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : Go(); const e = (d) => s(d); - if (e.version = "3.2.7", e.removed = [], !t || !t.document || t.document.nodeType !== ko || !t.Element) return e.isSupported = !1, e; + if (e.version = "3.2.7", e.removed = [], !t || !t.document || t.document.nodeType !== Ko || !t.Element) return e.isSupported = !1, e; let { document: i } = t; - const n = i, r = n.currentScript, { DocumentFragment: a, HTMLTemplateElement: o, Node: l, Element: c, NodeFilter: u, NamedNodeMap: m = t.NamedNodeMap || t.MozNamedAttrMap, HTMLFormElement: p, DOMParser: h, trustedTypes: b } = t, A = c.prototype, I = me(A, "cloneNode"), q = me(A, "remove"), R = me(A, "nextSibling"), $ = me(A, "childNodes"), _ = me(A, "parentNode"); + const n = i, r = n.currentScript, { DocumentFragment: a, HTMLTemplateElement: o, Node: l, Element: c, NodeFilter: u, NamedNodeMap: m = t.NamedNodeMap || t.MozNamedAttrMap, HTMLFormElement: p, DOMParser: h, trustedTypes: f } = t, A = c.prototype, I = be(A, "cloneNode"), $ = be(A, "remove"), R = be(A, "nextSibling"), U = be(A, "childNodes"), _ = be(A, "parentNode"); if (typeof o == "function") { const d = i.createElement("template"); d.content && d.content.ownerDocument && (i = d.content.ownerDocument); } let S, E = ""; - const { implementation: j, createNodeIterator: ct, createDocumentFragment: wt, getElementsByTagName: mt } = i, { importNode: li } = n; - let K = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] }; - e.isSupported = typeof Jr == "function" && typeof _ == "function" && j && j.createHTMLDocument !== void 0; - const { MUSTACHE_EXPR: Tt, ERB_EXPR: Vt, TMPLIT_EXPR: tt, DATA_ATTR: di, ARIA_ATTR: ci, IS_SCRIPT_OR_DATA: hi, ATTR_WHITESPACE: Ie, CUSTOM_ELEMENT: ui } = As; - let { IS_ALLOWED_URI: F } = As, D = null; - const Mn = L({}, [...bs, ...Ni, ...Pi, ...Fi, ..._s]); - let W = null; - const Nn = L({}, [...vs, ...Hi, ...ys, ...qe]); - let M = Object.seal(Yr(null, { tagNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, allowCustomizedBuiltInElements: { writable: !0, configurable: !1, enumerable: !0, value: !1 } })), re = null, mi = null, Pn = !0, gi = !0, Fn = !1, Hn = !0, jt = !1, Re = !0, kt = !1, pi = !1, fi = !1, Wt = !1, De = !1, Oe = !1, qn = !0, $n = !1, bi = !0, ae = !1, zt = {}, Kt = null; - const Un = L({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]); - let Vn = null; - const jn = L({}, ["audio", "video", "img", "source", "image", "track"]); - let _i = null; - const Wn = L({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), Be = "http://www.w3.org/1998/Math/MathML", Me = "http://www.w3.org/2000/svg", gt = "http://www.w3.org/1999/xhtml"; - let Gt = gt, vi = !1, yi = null; - const Da = L({}, [Be, Me, gt], Bi); - let Ne = L({}, ["mi", "mo", "mn", "ms", "mtext"]), Pe = L({}, ["annotation-xml"]); - const Oa = L({}, ["title", "style", "font", "a", "script"]); - let oe = null; - const Ba = ["application/xhtml+xml", "text/html"]; - let V = null, Jt = null; - const Ma = i.createElement("form"), zn = function(d) { + const { implementation: z, createNodeIterator: ct, createDocumentFragment: wt, getElementsByTagName: mt } = i, { importNode: bi } = n; + let V = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] }; + e.isSupported = typeof la == "function" && typeof _ == "function" && z && z.createHTMLDocument !== void 0; + const { MUSTACHE_EXPR: Tt, ERB_EXPR: jt, TMPLIT_EXPR: tt, DATA_ATTR: _i, ARIA_ATTR: vi, IS_SCRIPT_OR_DATA: yi, ATTR_WHITESPACE: Me, CUSTOM_ELEMENT: Ai } = Bs; + let { IS_ALLOWED_URI: oe } = Bs, M = null; + const H = L({}, [...Is, ...Ki, ...Gi, ...Ji, ...Rs]); + let D = null; + const Gn = L({}, [...Ds, ...Yi, ...Os, ...Ke]); + let N = Object.seal(da(null, { tagNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, allowCustomizedBuiltInElements: { writable: !0, configurable: !1, enumerable: !0, value: !1 } })), le = null, Ei = null, Jn = !0, xi = !0, Yn = !1, Xn = !0, Wt = !1, Ne = !0, kt = !1, Si = !1, Li = !1, zt = !1, Pe = !1, Fe = !1, Qn = !0, Zn = !1, Ci = !0, de = !1, Kt = {}, Gt = null; + const ts = L({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]); + let es = null; + const is = L({}, ["audio", "video", "img", "source", "image", "track"]); + let wi = null; + const ns = L({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), He = "http://www.w3.org/1998/Math/MathML", qe = "http://www.w3.org/2000/svg", gt = "http://www.w3.org/1999/xhtml"; + let Jt = gt, Ti = !1, ki = null; + const Ga = L({}, [He, qe, gt], Wi); + let $e = L({}, ["mi", "mo", "mn", "ms", "mtext"]), Ue = L({}, ["annotation-xml"]); + const Ja = L({}, ["title", "style", "font", "a", "script"]); + let ce = null; + const Ya = ["application/xhtml+xml", "text/html"]; + let W = null, Yt = null; + const Xa = i.createElement("form"), ss = function(d) { return d instanceof RegExp || d instanceof Function; - }, Ai = function() { + }, Ii = function() { let d = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; - if (!Jt || Jt !== d) { - if (d && typeof d == "object" || (d = {}), d = ft(d), oe = Ba.indexOf(d.PARSER_MEDIA_TYPE) === -1 ? "text/html" : d.PARSER_MEDIA_TYPE, V = oe === "application/xhtml+xml" ? Bi : Xe, D = lt(d, "ALLOWED_TAGS") ? L({}, d.ALLOWED_TAGS, V) : Mn, W = lt(d, "ALLOWED_ATTR") ? L({}, d.ALLOWED_ATTR, V) : Nn, yi = lt(d, "ALLOWED_NAMESPACES") ? L({}, d.ALLOWED_NAMESPACES, Bi) : Da, _i = lt(d, "ADD_URI_SAFE_ATTR") ? L(ft(Wn), d.ADD_URI_SAFE_ATTR, V) : Wn, Vn = lt(d, "ADD_DATA_URI_TAGS") ? L(ft(jn), d.ADD_DATA_URI_TAGS, V) : jn, Kt = lt(d, "FORBID_CONTENTS") ? L({}, d.FORBID_CONTENTS, V) : Un, re = lt(d, "FORBID_TAGS") ? L({}, d.FORBID_TAGS, V) : ft({}), mi = lt(d, "FORBID_ATTR") ? L({}, d.FORBID_ATTR, V) : ft({}), zt = !!lt(d, "USE_PROFILES") && d.USE_PROFILES, Pn = d.ALLOW_ARIA_ATTR !== !1, gi = d.ALLOW_DATA_ATTR !== !1, Fn = d.ALLOW_UNKNOWN_PROTOCOLS || !1, Hn = d.ALLOW_SELF_CLOSE_IN_ATTR !== !1, jt = d.SAFE_FOR_TEMPLATES || !1, Re = d.SAFE_FOR_XML !== !1, kt = d.WHOLE_DOCUMENT || !1, Wt = d.RETURN_DOM || !1, De = d.RETURN_DOM_FRAGMENT || !1, Oe = d.RETURN_TRUSTED_TYPE || !1, fi = d.FORCE_BODY || !1, qn = d.SANITIZE_DOM !== !1, $n = d.SANITIZE_NAMED_PROPS || !1, bi = d.KEEP_CONTENT !== !1, ae = d.IN_PLACE || !1, F = d.ALLOWED_URI_REGEXP || Xr, Gt = d.NAMESPACE || gt, Ne = d.MATHML_TEXT_INTEGRATION_POINTS || Ne, Pe = d.HTML_INTEGRATION_POINTS || Pe, M = d.CUSTOM_ELEMENT_HANDLING || {}, d.CUSTOM_ELEMENT_HANDLING && zn(d.CUSTOM_ELEMENT_HANDLING.tagNameCheck) && (M.tagNameCheck = d.CUSTOM_ELEMENT_HANDLING.tagNameCheck), d.CUSTOM_ELEMENT_HANDLING && zn(d.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) && (M.attributeNameCheck = d.CUSTOM_ELEMENT_HANDLING.attributeNameCheck), d.CUSTOM_ELEMENT_HANDLING && typeof d.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements == "boolean" && (M.allowCustomizedBuiltInElements = d.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements), jt && (gi = !1), De && (Wt = !0), zt && (D = L({}, _s), W = [], zt.html === !0 && (L(D, bs), L(W, vs)), zt.svg === !0 && (L(D, Ni), L(W, Hi), L(W, qe)), zt.svgFilters === !0 && (L(D, Pi), L(W, Hi), L(W, qe)), zt.mathMl === !0 && (L(D, Fi), L(W, ys), L(W, qe))), d.ADD_TAGS && (D === Mn && (D = ft(D)), L(D, d.ADD_TAGS, V)), d.ADD_ATTR && (W === Nn && (W = ft(W)), L(W, d.ADD_ATTR, V)), d.ADD_URI_SAFE_ATTR && L(_i, d.ADD_URI_SAFE_ATTR, V), d.FORBID_CONTENTS && (Kt === Un && (Kt = ft(Kt)), L(Kt, d.FORBID_CONTENTS, V)), bi && (D["#text"] = !0), kt && L(D, ["html", "head", "body"]), D.table && (L(D, ["tbody"]), delete re.tbody), d.TRUSTED_TYPES_POLICY) { - if (typeof d.TRUSTED_TYPES_POLICY.createHTML != "function") throw ue('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); - if (typeof d.TRUSTED_TYPES_POLICY.createScriptURL != "function") throw ue('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); + if (!Yt || Yt !== d) { + if (d && typeof d == "object" || (d = {}), d = ft(d), ce = Ya.indexOf(d.PARSER_MEDIA_TYPE) === -1 ? "text/html" : d.PARSER_MEDIA_TYPE, W = ce === "application/xhtml+xml" ? Wi : si, M = lt(d, "ALLOWED_TAGS") ? L({}, d.ALLOWED_TAGS, W) : H, D = lt(d, "ALLOWED_ATTR") ? L({}, d.ALLOWED_ATTR, W) : Gn, ki = lt(d, "ALLOWED_NAMESPACES") ? L({}, d.ALLOWED_NAMESPACES, Wi) : Ga, wi = lt(d, "ADD_URI_SAFE_ATTR") ? L(ft(ns), d.ADD_URI_SAFE_ATTR, W) : ns, es = lt(d, "ADD_DATA_URI_TAGS") ? L(ft(is), d.ADD_DATA_URI_TAGS, W) : is, Gt = lt(d, "FORBID_CONTENTS") ? L({}, d.FORBID_CONTENTS, W) : ts, le = lt(d, "FORBID_TAGS") ? L({}, d.FORBID_TAGS, W) : ft({}), Ei = lt(d, "FORBID_ATTR") ? L({}, d.FORBID_ATTR, W) : ft({}), Kt = !!lt(d, "USE_PROFILES") && d.USE_PROFILES, Jn = d.ALLOW_ARIA_ATTR !== !1, xi = d.ALLOW_DATA_ATTR !== !1, Yn = d.ALLOW_UNKNOWN_PROTOCOLS || !1, Xn = d.ALLOW_SELF_CLOSE_IN_ATTR !== !1, Wt = d.SAFE_FOR_TEMPLATES || !1, Ne = d.SAFE_FOR_XML !== !1, kt = d.WHOLE_DOCUMENT || !1, zt = d.RETURN_DOM || !1, Pe = d.RETURN_DOM_FRAGMENT || !1, Fe = d.RETURN_TRUSTED_TYPE || !1, Li = d.FORCE_BODY || !1, Qn = d.SANITIZE_DOM !== !1, Zn = d.SANITIZE_NAMED_PROPS || !1, Ci = d.KEEP_CONTENT !== !1, de = d.IN_PLACE || !1, oe = d.ALLOWED_URI_REGEXP || ca, Jt = d.NAMESPACE || gt, $e = d.MATHML_TEXT_INTEGRATION_POINTS || $e, Ue = d.HTML_INTEGRATION_POINTS || Ue, N = d.CUSTOM_ELEMENT_HANDLING || {}, d.CUSTOM_ELEMENT_HANDLING && ss(d.CUSTOM_ELEMENT_HANDLING.tagNameCheck) && (N.tagNameCheck = d.CUSTOM_ELEMENT_HANDLING.tagNameCheck), d.CUSTOM_ELEMENT_HANDLING && ss(d.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) && (N.attributeNameCheck = d.CUSTOM_ELEMENT_HANDLING.attributeNameCheck), d.CUSTOM_ELEMENT_HANDLING && typeof d.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements == "boolean" && (N.allowCustomizedBuiltInElements = d.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements), Wt && (xi = !1), Pe && (zt = !0), Kt && (M = L({}, Rs), D = [], Kt.html === !0 && (L(M, Is), L(D, Ds)), Kt.svg === !0 && (L(M, Ki), L(D, Yi), L(D, Ke)), Kt.svgFilters === !0 && (L(M, Gi), L(D, Yi), L(D, Ke)), Kt.mathMl === !0 && (L(M, Ji), L(D, Os), L(D, Ke))), d.ADD_TAGS && (M === H && (M = ft(M)), L(M, d.ADD_TAGS, W)), d.ADD_ATTR && (D === Gn && (D = ft(D)), L(D, d.ADD_ATTR, W)), d.ADD_URI_SAFE_ATTR && L(wi, d.ADD_URI_SAFE_ATTR, W), d.FORBID_CONTENTS && (Gt === ts && (Gt = ft(Gt)), L(Gt, d.FORBID_CONTENTS, W)), Ci && (M["#text"] = !0), kt && L(M, ["html", "head", "body"]), M.table && (L(M, ["tbody"]), delete le.tbody), d.TRUSTED_TYPES_POLICY) { + if (typeof d.TRUSTED_TYPES_POLICY.createHTML != "function") throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); + if (typeof d.TRUSTED_TYPES_POLICY.createScriptURL != "function") throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); S = d.TRUSTED_TYPES_POLICY, E = S.createHTML(""); - } else S === void 0 && (S = (function(f, g) { - if (typeof f != "object" || typeof f.createPolicy != "function") return null; + } else S === void 0 && (S = function(b, g) { + if (typeof b != "object" || typeof b.createPolicy != "function") return null; let C = null; const T = "data-tt-policy-suffix"; g && g.hasAttribute(T) && (C = g.getAttribute(T)); const y = "dompurify" + (C ? "#" + C : ""); try { - return f.createPolicy(y, { createHTML: (U) => U, createScriptURL: (U) => U }); + return b.createPolicy(y, { createHTML: (j) => j, createScriptURL: (j) => j }); } catch { return console.warn("TrustedTypes policy " + y + " could not be created."), null; } - })(b, r)), S !== null && typeof E == "string" && (E = S.createHTML("")); - Q && Q(d), Jt = d; + }(f, r)), S !== null && typeof E == "string" && (E = S.createHTML("")); + Q && Q(d), Yt = d; } - }, Kn = L({}, [...Ni, ...Pi, ...po]), Gn = L({}, [...Fi, ...fo]), ht = function(d) { - ce(e.removed, { element: d }); + }, rs = L({}, [...Ki, ...Gi, ...Oo]), as = L({}, [...Ji, ...Bo]), ht = function(d) { + ge(e.removed, { element: d }); try { _(d).removeChild(d); } catch { - q(d); + $(d); } - }, It = function(d, f) { + }, It = function(d, b) { try { - ce(e.removed, { attribute: f.getAttributeNode(d), from: f }); + ge(e.removed, { attribute: b.getAttributeNode(d), from: b }); } catch { - ce(e.removed, { attribute: null, from: f }); + ge(e.removed, { attribute: null, from: b }); } - if (f.removeAttribute(d), d === "is") if (Wt || De) try { - ht(f); + if (b.removeAttribute(d), d === "is") if (zt || Pe) try { + ht(b); } catch { } else try { - f.setAttribute(d, ""); + b.setAttribute(d, ""); } catch { } - }, Jn = function(d) { - let f = null, g = null; - if (fi) d = "