diff --git a/assets/js/uploader.js b/assets/js/uploader.js index ee6732cc..7f87ef9a 100644 --- a/assets/js/uploader.js +++ b/assets/js/uploader.js @@ -9,17 +9,28 @@ const invalid = "is-invalid"; - // Modal elements - const pouetM = document.getElementById("uploaderPouet"); + // Poeut modal elements + const pouetM = document.getElementById("uploader-pouet"); if (pouetM == null) { throw new Error("The uploader-pouet element is null."); } + pouetM.addEventListener("shown.bs.modal", function () { + poeutInput.focus(); + }); const pouetModal = new bootstrap.Modal(pouetM); + const poeutInput = document.getElementById("pouet-submission"); + if (poeutInput == null) { + throw new Error("The pouet-submission element is null."); + } - const demozooM = document.getElementById("uploader-demozoo"); + // Demozoo modal elements + const demozooM = document.getElementById("uploader-demozoo"); if (demozooM == null) { throw new Error("The uploader-demozoo element is null."); } + demozooM.addEventListener("shown.bs.modal", function () { + demozooInput.focus(); + }); const demozooModal = new bootstrap.Modal(demozooM); const demozooInput = document.getElementById("demozoo-submission"); if (demozooInput == null) { @@ -71,30 +82,38 @@ // Keyboard shortcuts event listener document.addEventListener("keydown", function (event) { + const demozoo = "d", + pouet = "p", + intro = "i", + nfo = "n", + graphic = "g", + magazine = "m", + advanced = "a", + glossaryOfTerms = "t"; if (event.ctrlKey && event.altKey) { switch (event.key) { - case "d": + case demozoo: demozooModal.show(); break; - case "p": + case pouet: pouetModal.show(); break; - case "i": + case intro: demozooModal.show(); break; - case "n": // n for nfo + case nfo: txtModal.show(); break; - case "g": // g for gfx + case graphic: imgModal.show(); break; - case "m": + case magazine: magModal.show(); break; - case "a": + case advanced: advModal.show(); break; - case "t": // t for terms + case glossaryOfTerms: glossModal.show(); break; } @@ -186,17 +205,6 @@ const imgFrm = document.getElementById("imageUploader"); const magFrm = document.getElementById("magUploader"); const advFrm = document.getElementById("advancedUploader"); - // Focus on the first input field when the modal is shown - demozooM.addEventListener("shown.bs.modal", function () { - demozooInput.focus(); - }); - // Focus on the first input field when the modal is shown - const pouetId = document.getElementById("pouetProdsID"); - document - .getElementById("uploaderPouet") - .addEventListener("shown.bs.modal", function () { - pouetId.focus(); - }); // Elements for the intro uploader const introFile = document.getElementById("introFile"); diff --git a/handler/app/context.go b/handler/app/context.go index 73bceb5f..6e781bf9 100644 --- a/handler/app/context.go +++ b/handler/app/context.go @@ -1041,7 +1041,7 @@ func ProdPouet(logr *zap.SugaredLogger, c echo.Context, id string) error { if logr == nil { return InternalErr(logr, c, name, ErrZap) } - p := pouet.Pouet{} + p := pouet.Production{} i, err := strconv.Atoi(id) if err != nil { return c.String(http.StatusNotFound, err.Error()) diff --git a/handler/htmx/htmx.go b/handler/htmx/htmx.go index 5d83d4d0..0697a475 100644 --- a/handler/htmx/htmx.go +++ b/handler/htmx/htmx.go @@ -31,6 +31,12 @@ func Routes(logr *zap.SugaredLogger, e *echo.Echo) *echo.Echo { submit.POST("/demozoo/production/submit/:id", func(x echo.Context) error { return DemozooSubmit(logr, x) }) + submit.POST("/pouet/production", func(x echo.Context) error { + return PouetProd(x) + }) + submit.POST("/pouet/production/submit/:id", func(x echo.Context) error { + return PouetSubmit(logr, x) + }) submit.POST("/search/releaser", func(x echo.Context) error { return SearchReleaser(logr, x) }) diff --git a/handler/htmx/pouet.go b/handler/htmx/pouet.go new file mode 100644 index 00000000..d781d8d4 --- /dev/null +++ b/handler/htmx/pouet.go @@ -0,0 +1,188 @@ +package htmx + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/Defacto2/server/internal/helper" + "github.com/Defacto2/server/internal/postgres" + "github.com/Defacto2/server/internal/pouet" + "github.com/Defacto2/server/model" + "github.com/labstack/echo/v4" + "go.uber.org/zap" +) + +// PouetProd fetches the multiple download_links values from the +// Pouet production API and attempts to download and save one of the +// linked files. If multiple links are found, the first link is used as +// they should all point to the same asset. +// +// Both the Pouet production ID param and the Defacto2 UUID query +// param values are required as params to fetch the production data and +// to save the file to the correct filename. +func PouetProd(c echo.Context) error { + sid := c.FormValue("pouet-submission") + id, err := strconv.Atoi(sid) + if err != nil { + return c.String(http.StatusNotAcceptable, + "The Pouet production ID must be a numeric value, "+sid) + } + + db, err := postgres.ConnectDB() + if err != nil { + return ErrDB + } + defer db.Close() + ctx := context.Background() + + deleted, key, err := model.FindPouetFile(ctx, db, int64(id)) + if err != nil { + return c.String(http.StatusServiceUnavailable, + "error, the database query failed") + } + if key != 0 && !deleted { + html := fmt.Sprintf("This Pouet production is already in use.", helper.ObfuscateID(key)) + return c.HTML(http.StatusOK, html) + } + if key != 0 && deleted { + return c.HTML(http.StatusOK, "This Pouet production is already in use.") + } + + resp, err := PouetValid(c, id) + if err != nil { + return err + } else if resp.Prod.ID == "" { + return nil + } + if !resp.Success { + return c.String(http.StatusNotFound, "error, the Pouet production ID is not found") + } + + prod := resp.Prod + if pid, err := strconv.Atoi(prod.ID); err != nil { + return c.String(http.StatusNotFound, "error, the Pouet production ID is invalid") + } else if pid < 1 { + return nil + } + + info := []string{prod.Title} + if len(prod.Groups) > 0 { + info = append(info, "by") + for _, a := range prod.Groups { + info = append(info, a.Name) + } + } + if prod.ReleaseDate != "" { + info = append(info, "on", prod.ReleaseDate) + } + if prod.Platfs.String() != "" { + info = append(info, "for", prod.Platfs.String()) + } + + html := `
` + html += fmt.Sprintf(``, id, id) + html += `
` + html += fmt.Sprintf(`

%s

`, strings.Join(info, " ")) + return c.HTML(http.StatusOK, html) +} + +// PouetValid fetches the first usable download link from the Pouet API. +// The production ID is validated and the production is checked to see if it +// is suitable for Defacto2. If the production is not suitable, an empty +// production is returned with a htmx message. +func PouetValid(c echo.Context, id int) (pouet.Response, error) { + if id < 1 { + return pouet.Response{}, + c.String(http.StatusNotAcceptable, fmt.Sprintf("invalid id: %d", id)) + } + + var prod pouet.Response + if err := prod.Get(id); err != nil { + return pouet.Response{}, c.String(http.StatusNotFound, err.Error()) + } + + plat := prod.Prod.Platfs + sect := prod.Prod.Types + if !plat.Valid() || !sect.Valid() { + return pouet.Response{}, c.HTML(http.StatusOK, + fmt.Sprintf("Production %d is probably not suitable for Defacto2."+ + "
A production must an intro, demo or cracktro either for MsDos or Windows.", id)) + } + + var valid string + if prod.Prod.Download != "" { + valid = prod.Prod.Download + } + + for _, link := range prod.Prod.DownloadLinks { + if valid != "" { + break + } + if link.Link == "" { + continue + } + switch strings.ToLower(link.Type) { + case "youtube": + continue + } + valid = link.Link + break + } + if valid == "" { + return pouet.Response{}, + c.String(http.StatusOK, "This Pouet production has no suitable download links.") + } + return prod, nil +} + +// PouetSubmit is the handler for the /pouet/production/submit route. +// This will attempt to insert a new file record into the database using +// the Pouet production ID. If the Pouet production ID is already in +// use, an error message is returned. +func PouetSubmit(logr *zap.SugaredLogger, c echo.Context) error { + if logr == nil { + return c.String(http.StatusInternalServerError, + "error, pouet submit logger is nil") + } + + sid := c.Param("id") + id, err := strconv.ParseUint(sid, 10, 64) + if err != nil { + return c.String(http.StatusNotAcceptable, + "The Pouet production ID must be a numeric value, "+sid) + } + if id < 1 || id > pouet.Sanity { + return c.String(http.StatusNotAcceptable, + "The Pouet production ID is invalid, "+sid) + } + + db, err := postgres.ConnectDB() + if err != nil { + return ErrDB + } + defer db.Close() + ctx := context.Background() + + if exist, err := model.ExistPouetFile(ctx, db, int64(id)); err != nil { + return c.String(http.StatusServiceUnavailable, + "error, the database query failed") + } else if exist { + return c.String(http.StatusForbidden, + "error, the pouet key is already in use") + } + + key, err := model.InsertPouetFile(ctx, db, int64(id)) + if err != nil || key == 0 { + logr.Error(err, id) + return c.String(http.StatusServiceUnavailable, + "error, the database insert failed") + } + + html := fmt.Sprintf("Thanks for the submission of Pouet production: %d", id) + return c.HTML(http.StatusOK, html) +} diff --git a/internal/pouet/pouet.go b/internal/pouet/pouet.go index af46ddf0..28064415 100644 --- a/internal/pouet/pouet.go +++ b/internal/pouet/pouet.go @@ -23,6 +23,8 @@ const ( Timeout = 5 * time.Second // StarRounder is the rounding value for the stars rating. StarRounder = 0.5 + // Sanity is to check the maximum permitted production ID. + Sanity = 200000 // firstID is the first production ID on Pouet. firstID = 1 ) @@ -33,12 +35,12 @@ var ( ErrStatus = errors.New("pouet production status is not ok") ) -// Pouet is the production data from the Pouet API. +// Production is the production data from the Pouet API. // The Pouet API returns values as null or string, so this struct // is used to normalize the data types. -type Pouet struct { +type Production struct { // Platforms are the platforms the prod runs on. - Platforms Platforms `json:"platforms"` + Platforms Platfs `json:"platforms"` // Title is the prod title. Title string `json:"title"` // ReleaseDate is the prod release date. @@ -83,34 +85,36 @@ type Votes struct { // Response is the JSON response from the Pouet API with production voting data. type Response struct { Prod struct { - // used by uploader and voter - ID string `json:"id"` - // used by voter - Voteup string `json:"voteup"` - Votepig string `json:"votepig"` - Votedown string `json:"votedown"` - Voteavg string `json:"voteavg"` - // used by uploader - Title string `json:"name"` - ReleaseDate string `json:"releaseDate"` + ID string `json:"id"` // ID is the prod ID. + Voteup string `json:"voteup"` // Voteup is the number of thumbs up votes. + Votepig string `json:"votepig"` // Votepig is the number of meh votes. + Votedown string `json:"votedown"` // Votedown is the number of thumbs down votes. + Voteavg string `json:"voteavg"` // Voteavg is the average votes, the maximum value is 1.0. + Title string `json:"name"` // Title is the prod title. + ReleaseDate string `json:"releaseDate"` // ReleaseDate is the prod release date. Groups []struct { ID string `json:"id"` Name string `json:"name"` - } `json:"groups"` - Platforms Platforms `json:"platforms"` - Types Types `json:"types"` - } `json:"prod"` - Success bool `json:"success"` + } `json:"groups"` // Groups are the releasers that produced the prod. + Platfs Platfs `json:"platforms"` // Platforms are the platforms the prod runs on. + Types Types `json:"types"` // Types are the prod types. + Download string `json:"download"` // Download is the first download link. + DownloadLinks []struct { + Type string `json:"type"` + Link string `json:"link"` + } `json:"downloadLinks"` // DownloadLinks are the additional download links. + } `json:"prod"` // Prod is the production data. + Success bool `json:"success"` // Success is true if the prod data was found. } -// Platforms are the supported platforms from the Pouet API. -type Platforms struct { - DosGus Platform `json:"69"` // MS-Dos with GUS - Windows Platform `json:"68"` // Windows - MSDos Platform `json:"67"` // MS-Dos +// Platfs are the supported platforms from the Pouet API. +type Platfs struct { + DosGus Platf `json:"69"` // MS-Dos with GUS + Windows Platf `json:"68"` // Windows + MSDos Platf `json:"67"` // MS-Dos } -func (p Platforms) String() string { +func (p Platfs) String() string { s := []string{} if p.DosGus.Name != "" { s = append(s, p.DosGus.Name) @@ -124,7 +128,7 @@ func (p Platforms) String() string { return strings.Join(s, ", ") } -func (p Platforms) Valid() bool { +func (p Platfs) Valid() bool { if p.DosGus.Slug == "msdosgus" { return true } @@ -137,8 +141,8 @@ func (p Platforms) Valid() bool { return false } -// Platform is the production platform data from the Pouet API. -type Platform struct { +// Platf is the production platform data from the Pouet API. +type Platf struct { Name string `json:"name"` Slug string `json:"slug"` } @@ -217,7 +221,7 @@ func (r *Response) Get(id int) error { // Uploader retrieves and parses the production data from the Pouet API. // The id value is the Pouet production ID and must be greater than 0. // The data is intended for the Pouet Uploader. -func (p *Pouet) Uploader(id int) error { +func (p *Production) Uploader(id int) error { if id < firstID { return fmt.Errorf("%w: %d", ErrID, id) } @@ -233,10 +237,10 @@ func (p *Pouet) Uploader(id int) error { p.Title = r.Prod.Title p.ReleaseDate = r.Prod.ReleaseDate p.Groups = r.Prod.Groups - p.Platforms = r.Prod.Platforms + p.Platforms = r.Prod.Platfs p.Types = r.Prod.Types - p.Platform = r.Prod.Platforms.String() - p.Valid = r.Prod.Platforms.Valid() && r.Prod.Types.Valid() + p.Platform = r.Prod.Platfs.String() + p.Valid = r.Prod.Platfs.Valid() && r.Prod.Types.Valid() return nil } diff --git a/internal/pouet/pouet_test.go b/internal/pouet/pouet_test.go index 316ea0ec..53e5529e 100644 --- a/internal/pouet/pouet_test.go +++ b/internal/pouet/pouet_test.go @@ -13,8 +13,8 @@ const testRemoteServers = false func TestPlatforms(t *testing.T) { t.Parallel() - p := pouet.Platforms{ - DosGus: pouet.Platform{ + p := pouet.Platfs{ + DosGus: pouet.Platf{ Name: "DOS/GUS", Slug: "msdosgus", }, @@ -46,7 +46,7 @@ func TestResponseGet(t *testing.T) { func TestPouet(t *testing.T) { t.Parallel() - p := pouet.Pouet{} + p := pouet.Production{} err := p.Uploader(0) require.Error(t, err) // this pings a remote server, so it is disabled. diff --git a/model/file.go b/model/file.go index d2823d4f..c8402588 100644 --- a/model/file.go +++ b/model/file.go @@ -91,6 +91,62 @@ func InsertDemozooFile(ctx context.Context, db *sql.DB, id int64) (int64, error) return f.ID, nil } +// ExistPouetFile returns true if the file record exists in the database using a Pouet production ID. +// This function will also return true for records that have been marked as deleted. +func ExistPouetFile(ctx context.Context, db *sql.DB, id int64) (bool, error) { + if db == nil { + return false, ErrDB + } + return models.Files(models.FileWhere.WebIDPouet.EQ(null.Int64From(id)), qm.WithDeleted()).Exists(ctx, db) +} + +// FindPouetFile retrieves the ID or key of a single file record from the database using a Pouet production ID. +// This function will also return records that have been marked as deleted and flag those with the boolean. +// If the record is not found then the function will return an ID of 0 but without an error. +func FindPouetFile(ctx context.Context, db *sql.DB, id int64) (bool, int64, error) { + if db == nil { + return false, 0, ErrDB + } + f, err := models.Files( + qm.Select("id", "deletedat"), + models.FileWhere.WebIDPouet.EQ(null.Int64From(id)), + qm.WithDeleted()).One(ctx, db) + if errors.Is(err, sql.ErrNoRows) { + return false, 0, nil + } + if err != nil { + return false, 0, err + } + deleted := !f.Deletedat.IsZero() + return deleted, f.ID, nil +} + +// InsertPouetFile inserts a new file record into the database using a Pouet production ID. +// This will not check if the Pouet production ID already exists in the database. +// When successful the function will return the new record ID. +func InsertPouetFile(ctx context.Context, db *sql.DB, id int64) (int64, error) { + if db == nil { + return 0, ErrDB + } + if id < startID || id > demozoo.Sanity { + return 0, fmt.Errorf("%w: %d", ErrID, id) + } + uid, err := uuid.NewV7() + if err != nil { + return 0, err + } + now := time.Now() + f := models.File{ + UUID: null.StringFrom(uid.String()), + WebIDPouet: null.Int64From(id), + Deletedat: null.TimeFromPtr(&now), + } + if err = f.Insert(ctx, db, boil.Infer()); err != nil { + return 0, err + } + return f.ID, nil +} + // FindObf retrieves a single file record from the database using the obfuscated record key. func FindObf(key string) (*models.File, error) { return recordObf(false, key) diff --git a/public/js/uploader.min.js b/public/js/uploader.min.js index 519376bc..eea4929f 100644 --- a/public/js/uploader.min.js +++ b/public/js/uploader.min.js @@ -1,2 +1,2 @@ /* uploader.min.js © Defacto2 2024 */ -(()=>{"use strict";const t="is-invalid",U=document.getElementById("uploaderPouet");if(U==null)throw new Error("The uploader-pouet element is null.");const le=new bootstrap.Modal(U),m=document.getElementById("uploader-demozoo");if(m==null)throw new Error("The uploader-demozoo element is null.");const A=new bootstrap.Modal(m),C=document.getElementById("demozoo-submission");if(C==null)throw new Error("The demozoo-submission element is null.");const oe=document.getElementById("uploaderText"),de=document.getElementById("uploaderImg"),ie=document.getElementById("uploaderMag"),ce=document.getElementById("uploaderAdv"),me=document.getElementById("termsModal"),ue=new bootstrap.Modal(oe),re=new bootstrap.Modal(de),fe=new bootstrap.Modal(ie),ge=new bootstrap.Modal(ce),ve=new bootstrap.Modal(me),$=document.getElementById("paginationStart"),O=document.getElementById("paginationPrev"),j=document.getElementById("paginationPrev2"),J=document.getElementById("paginationNext"),q=document.getElementById("paginationNext2"),G=document.getElementById("paginationEnd"),n=document.getElementById("paginationRange");if(typeof n<"u"&&n!=null){n.addEventListener("change",function(){const s=n.value,a=new URL(window.location.href);let l=a.pathname.split("/");const ne=l[l.length-1];!isNaN(ne)&&typeof Number(ne)=="number"?l[l.length-1]=s:l.push(s),a.pathname=l.join("/"),window.location.href=a.href});const e=document.getElementById("paginationRangeLabel");n.addEventListener("input",function(){e.textContent="Jump to page "+n.value})}document.addEventListener("keydown",function(e){if(e.ctrlKey&&e.altKey)switch(e.key){case"d":A.show();break;case"p":le.show();break;case"i":A.show();break;case"n":ue.show();break;case"g":re.show();break;case"m":fe.show();break;case"a":ge.show();break;case"t":ve.show();break}const s="ArrowRight",a="ArrowLeft";if(e.ctrlKey&&e.key==a){$?.click();return}if(e.ctrlKey&&e.key==s){G?.click();return}if(e.shiftKey&&e.key==a){j?.click();return}if(e.shiftKey&&e.key==s){q?.click();return}if(e.key==a){O?.click();return}if(e.key==s){J?.click();return}});function o(e){if(`${e}`=="")return!0;const s=new Date().getFullYear();return!(e<1980||e>s)}function d(e){return`${e}`==""?!0:!(e<1||e>12)}function H(e){return`${e}`==""?!0:!(e<1||e>31)}const Q=document.getElementById("introUploader"),V=document.getElementById("textUploader"),W=document.getElementById("imageUploader"),X=document.getElementById("magUploader"),Z=document.getElementById("advancedUploader");m.addEventListener("shown.bs.modal",function(){C.focus()});const Ee=document.getElementById("pouetProdsID");document.getElementById("uploaderPouet").addEventListener("shown.bs.modal",function(){Ee.focus()});const u=document.getElementById("introFile"),r=document.getElementById("releaseTitle"),f=document.getElementById("introReleasers"),g=document.getElementById("introYear"),v=document.getElementById("introMonth");function _(){u.classList.remove(t),r.classList.remove(t),f.classList.remove(t),g.classList.remove(t),v.classList.remove(t)}document.getElementById("introSubmit").addEventListener("click",function(){let e=!0;_(),u.value==""&&(u.classList.add(t),e=!1),r.value==""&&(r.classList.add(t),e=!1),f.value==""&&(f.classList.add(t),e=!1),o(g.value)==!1&&(g.classList.add(t),e=!1),d(v.value)==!1&&(v.classList.add(t),e=!1),e==!0&&Q.submit()}),Q.addEventListener("reset",_);const E=document.getElementById("textFile"),L=document.getElementById("textTitle"),y=document.getElementById("textReleasers"),I=document.getElementById("textYear"),p=document.getElementById("textMonth");function ee(){E.classList.remove(t),L.classList.remove(t),y.classList.remove(t),I.classList.remove(t),p.classList.remove(t)}document.getElementById("textSubmit").addEventListener("click",function(){let e=!0;ee(),E.value==""&&(E.classList.add(t),e=!1),L.value==""&&(L.classList.add(t),e=!1),y.value==""&&(y.classList.add(t),e=!1),o(I.value)==!1&&(I.classList.add(t),e=!1),d(p.value)==!1&&(p.classList.add(t),e=!1),e==!0&&V.submit()}),V.addEventListener("reset",ee);const B=document.getElementById("imageFile"),h=document.getElementById("imageTitle"),M=document.getElementById("imageReleasers"),b=document.getElementById("imageYear"),w=document.getElementById("imageMonth");function te(){B.classList.remove(t),h.classList.remove(t),M.classList.remove(t),b.classList.remove(t),w.classList.remove(t)}document.getElementById("imageSubmit").addEventListener("click",function(){let e=!0;te(),B.value==""&&(B.classList.add(t),e=!1),h.value==""&&(h.classList.add(t),e=!1),M.value==""&&(M.classList.add(t),e=!1),o(b.value)==!1&&(b.classList.add(t),e=!1),d(w.value)==!1&&(w.classList.add(t),e=!1),e==!0&&W.submit()}),W.addEventListener("reset",te);const k=document.getElementById("magFile"),x=document.getElementById("magTitle"),R=document.getElementById("magIssue"),F=document.getElementById("magYear"),T=document.getElementById("magMonth"),Y=document.getElementById("magDay");function se(){k.classList.remove(t),x.classList.remove(t),R.classList.remove(t),F.classList.remove(t),T.classList.remove(t),Y.classList.remove(t)}document.getElementById("magSubmit").addEventListener("click",function(){let e=!0;se(),k.value==""&&(k.classList.add(t),e=!1),x.value==""&&(x.classList.add(t),e=!1),R.value==""&&(R.classList.add(t),e=!1),o(F.value)==!1&&(F.classList.add(t),e=!1),d(T.value)==!1&&(T.classList.add(t),e=!1),H(Y.value)==!1&&(Y.classList.add(t),e=!1),e==!0&&X.submit()}),X.addEventListener("reset",se);const S=document.getElementById("advFile"),i=document.getElementById("advSelOS"),c=document.getElementById("advSelCat"),z=document.getElementById("advTitle"),D=document.getElementById("releasersAdv"),N=document.getElementById("advYear"),P=document.getElementById("advMonth"),K=document.getElementById("advDay");function ae(){S.classList.remove(t),i.classList.remove(t),c.classList.remove(t),z.classList.remove(t),D.classList.remove(t),N.classList.remove(t),P.classList.remove(t),K.classList.remove(t)}document.getElementById("advSubmit").addEventListener("click",function(){const e="Choose...";let s=!0;ae(),S.value==""&&(S.classList.add(t),s=!1),(i.value==""||i.value==e)&&(i.classList.add(t),s=!1),(c.value==""||c.value==e)&&(c.classList.add(t),s=!1),z.value==""&&(z.classList.add(t),s=!1),D.value==""&&(D.classList.add(t),s=!1),o(N.value)==!1&&(N.classList.add(t),s=!1),d(P.value)==!1&&(P.classList.add(t),s=!1),H(K.value)==!1&&(K.classList.add(t),s=!1),s==!0&&Z.submit()}),Z.addEventListener("reset",ae)})(); +(()=>{"use strict";const t="is-invalid",r=document.getElementById("uploader-pouet");if(r==null)throw new Error("The uploader-pouet element is null.");r.addEventListener("shown.bs.modal",function(){$.focus()});const ce=new bootstrap.Modal(r),$=document.getElementById("pouet-submission");if($==null)throw new Error("The pouet-submission element is null.");const u=document.getElementById("uploader-demozoo");if(u==null)throw new Error("The uploader-demozoo element is null.");u.addEventListener("shown.bs.modal",function(){J.focus()});const j=new bootstrap.Modal(u),J=document.getElementById("demozoo-submission");if(J==null)throw new Error("The demozoo-submission element is null.");const me=document.getElementById("uploaderText"),re=document.getElementById("uploaderImg"),ue=document.getElementById("uploaderMag"),fe=document.getElementById("uploaderAdv"),ge=document.getElementById("termsModal"),ve=new bootstrap.Modal(me),Ee=new bootstrap.Modal(re),Le=new bootstrap.Modal(ue),ye=new bootstrap.Modal(fe),pe=new bootstrap.Modal(ge),q=document.getElementById("paginationStart"),G=document.getElementById("paginationPrev"),H=document.getElementById("paginationPrev2"),Q=document.getElementById("paginationNext"),V=document.getElementById("paginationNext2"),W=document.getElementById("paginationEnd"),n=document.getElementById("paginationRange");if(typeof n<"u"&&n!=null){n.addEventListener("change",function(){const s=n.value,d=new URL(window.location.href);let a=d.pathname.split("/");const m=a[a.length-1];!isNaN(m)&&typeof Number(m)=="number"?a[a.length-1]=s:a.push(s),d.pathname=a.join("/"),window.location.href=d.href});const e=document.getElementById("paginationRangeLabel");n.addEventListener("input",function(){e.textContent="Jump to page "+n.value})}document.addEventListener("keydown",function(e){const s="d",d="p",ie="i",a="n",m="g",Ie="m",Be="a",he="t";if(e.ctrlKey&&e.altKey)switch(e.key){case s:j.show();break;case d:ce.show();break;case ie:j.show();break;case a:ve.show();break;case m:Ee.show();break;case Ie:Le.show();break;case Be:ye.show();break;case he:pe.show();break}const P="ArrowRight",O="ArrowLeft";if(e.ctrlKey&&e.key==O){q?.click();return}if(e.ctrlKey&&e.key==P){W?.click();return}if(e.shiftKey&&e.key==O){H?.click();return}if(e.shiftKey&&e.key==P){V?.click();return}if(e.key==O){G?.click();return}if(e.key==P){Q?.click();return}});function l(e){if(`${e}`=="")return!0;const s=new Date().getFullYear();return!(e<1980||e>s)}function o(e){return`${e}`==""?!0:!(e<1||e>12)}function X(e){return`${e}`==""?!0:!(e<1||e>31)}const Z=document.getElementById("introUploader"),_=document.getElementById("textUploader"),ee=document.getElementById("imageUploader"),te=document.getElementById("magUploader"),se=document.getElementById("advancedUploader"),f=document.getElementById("introFile"),g=document.getElementById("releaseTitle"),v=document.getElementById("introReleasers"),E=document.getElementById("introYear"),L=document.getElementById("introMonth");function ae(){f.classList.remove(t),g.classList.remove(t),v.classList.remove(t),E.classList.remove(t),L.classList.remove(t)}document.getElementById("introSubmit").addEventListener("click",function(){let e=!0;ae(),f.value==""&&(f.classList.add(t),e=!1),g.value==""&&(g.classList.add(t),e=!1),v.value==""&&(v.classList.add(t),e=!1),l(E.value)==!1&&(E.classList.add(t),e=!1),o(L.value)==!1&&(L.classList.add(t),e=!1),e==!0&&Z.submit()}),Z.addEventListener("reset",ae);const y=document.getElementById("textFile"),p=document.getElementById("textTitle"),I=document.getElementById("textReleasers"),B=document.getElementById("textYear"),h=document.getElementById("textMonth");function ne(){y.classList.remove(t),p.classList.remove(t),I.classList.remove(t),B.classList.remove(t),h.classList.remove(t)}document.getElementById("textSubmit").addEventListener("click",function(){let e=!0;ne(),y.value==""&&(y.classList.add(t),e=!1),p.value==""&&(p.classList.add(t),e=!1),I.value==""&&(I.classList.add(t),e=!1),l(B.value)==!1&&(B.classList.add(t),e=!1),o(h.value)==!1&&(h.classList.add(t),e=!1),e==!0&&_.submit()}),_.addEventListener("reset",ne);const b=document.getElementById("imageFile"),w=document.getElementById("imageTitle"),M=document.getElementById("imageReleasers"),k=document.getElementById("imageYear"),x=document.getElementById("imageMonth");function le(){b.classList.remove(t),w.classList.remove(t),M.classList.remove(t),k.classList.remove(t),x.classList.remove(t)}document.getElementById("imageSubmit").addEventListener("click",function(){let e=!0;le(),b.value==""&&(b.classList.add(t),e=!1),w.value==""&&(w.classList.add(t),e=!1),M.value==""&&(M.classList.add(t),e=!1),l(k.value)==!1&&(k.classList.add(t),e=!1),o(x.value)==!1&&(x.classList.add(t),e=!1),e==!0&&ee.submit()}),ee.addEventListener("reset",le);const R=document.getElementById("magFile"),F=document.getElementById("magTitle"),T=document.getElementById("magIssue"),Y=document.getElementById("magYear"),S=document.getElementById("magMonth"),z=document.getElementById("magDay");function oe(){R.classList.remove(t),F.classList.remove(t),T.classList.remove(t),Y.classList.remove(t),S.classList.remove(t),z.classList.remove(t)}document.getElementById("magSubmit").addEventListener("click",function(){let e=!0;oe(),R.value==""&&(R.classList.add(t),e=!1),F.value==""&&(F.classList.add(t),e=!1),T.value==""&&(T.classList.add(t),e=!1),l(Y.value)==!1&&(Y.classList.add(t),e=!1),o(S.value)==!1&&(S.classList.add(t),e=!1),X(z.value)==!1&&(z.classList.add(t),e=!1),e==!0&&te.submit()}),te.addEventListener("reset",oe);const N=document.getElementById("advFile"),i=document.getElementById("advSelOS"),c=document.getElementById("advSelCat"),D=document.getElementById("advTitle"),K=document.getElementById("releasersAdv"),U=document.getElementById("advYear"),A=document.getElementById("advMonth"),C=document.getElementById("advDay");function de(){N.classList.remove(t),i.classList.remove(t),c.classList.remove(t),D.classList.remove(t),K.classList.remove(t),U.classList.remove(t),A.classList.remove(t),C.classList.remove(t)}document.getElementById("advSubmit").addEventListener("click",function(){const e="Choose...";let s=!0;de(),N.value==""&&(N.classList.add(t),s=!1),(i.value==""||i.value==e)&&(i.classList.add(t),s=!1),(c.value==""||c.value==e)&&(c.classList.add(t),s=!1),D.value==""&&(D.classList.add(t),s=!1),K.value==""&&(K.classList.add(t),s=!1),l(U.value)==!1&&(U.classList.add(t),s=!1),o(A.value)==!1&&(A.classList.add(t),s=!1),X(C.value)==!1&&(C.classList.add(t),s=!1),s==!0&&se.submit()}),se.addEventListener("reset",de)})(); diff --git a/view/app/layout_uploader.tmpl b/view/app/layout_uploader.tmpl index 8ad1315f..b8643a14 100644 --- a/view/app/layout_uploader.tmpl +++ b/view/app/layout_uploader.tmpl @@ -7,7 +7,7 @@