2026-05-20 18:25:15 +02:00
|
|
|
import { FSUploader } from "./SubmissionsClass/FSUploader.js";
|
|
|
|
|
import { HashesManager } from "./SubmissionsClass/HashesManager.js";
|
|
|
|
|
import { GameSelector } from "./SubmissionsClass/GameSelector.js";
|
|
|
|
|
import { MainImageManager } from "./SubmissionsClass/MainImageManager.js";
|
|
|
|
|
import { GalleryManager } from "./SubmissionsClass/GalleryManager.js";
|
|
|
|
|
import { Credits } from "./SubmissionsClass/Credits.js";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If there is some server side errors.
|
|
|
|
|
* We may need reload some things.
|
|
|
|
|
* @type {boolean}
|
|
|
|
|
*/
|
|
|
|
|
const SERVER_SIDE_ERRORS = document.querySelector('meta[name="submission-has-errors"]')?.content === '1';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Object map of errors messages
|
|
|
|
|
* @type {Object<string,string>}
|
|
|
|
|
*/
|
|
|
|
|
const ERROR_TABLE = {
|
|
|
|
|
isUploading: "A file is uploading. Please wait.",
|
|
|
|
|
noFiles: "Please select a file to upload",
|
|
|
|
|
uploadError: "One or more files failed to upload.",
|
|
|
|
|
notAllFilesDone: "Not all the files have finished uploading yet.",
|
|
|
|
|
noModifications: "Please select at least a type of hack.",
|
2026-06-10 11:04:26 +02:00
|
|
|
noSystems: "Please select at least a system.",
|
2026-05-20 18:25:15 +02:00
|
|
|
noDescription: "Please provide a description.",
|
|
|
|
|
noGame: "Please provide a game or create a new one and fill all the required fields.",
|
|
|
|
|
noLanguages: "Please select at least a language.",
|
|
|
|
|
noAuthors: "Please provide at least an author or create a new one and fill all the required fields.",
|
2026-06-23 19:24:38 +02:00
|
|
|
noMainImage: "Please upload a main image.",
|
2026-05-20 18:25:15 +02:00
|
|
|
noGalleryImages: "Please select at least a gallery image.",
|
|
|
|
|
isSubmitting: "The entry is already during submission."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Current section.
|
|
|
|
|
* @returns {string}
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
const SECTION = () => document.querySelector("meta[name='fs-section']")?.content ?? '';
|
2026-06-16 16:21:43 +02:00
|
|
|
const CSRF = () => document.querySelector("meta[name='csrf-token']")?.content ?? '';
|
2026-05-20 18:25:15 +02:00
|
|
|
|
|
|
|
|
window.FSUploader = FSUploader;
|
|
|
|
|
window.HashesManager = HashesManager;
|
|
|
|
|
window.GameSelector = GameSelector;
|
|
|
|
|
window.MainImageManager = MainImageManager;
|
|
|
|
|
window.GalleryManager = GalleryManager;
|
|
|
|
|
window.Credits = Credits;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one checkbox is checked in this element.
|
|
|
|
|
* @param {HTMLElement} element
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function verifyCheckboxes( element ){
|
|
|
|
|
if( !parent ) return false;
|
|
|
|
|
return Array.from(element.querySelectorAll('input[type="checkbox"]')).some(el => el.checked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if an EasyMDE field is filled.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} fieldName
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function verifyMDE( fieldName ){
|
|
|
|
|
const textarea = document.querySelector('#field_' + fieldName);
|
|
|
|
|
if( textarea && textarea.value.trim().length > 0 ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const field = window['mde_' + fieldName] || null;
|
|
|
|
|
return field && typeof field.value === 'function' && field.value().trim().length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.SubmissionVerifications = {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if we are in an upload.
|
|
|
|
|
* @param {FSUploader} Uploader
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step1_DuringFSUpload: function( Uploader ){
|
|
|
|
|
return !Uploader.isUploading;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one file is uploaded.
|
|
|
|
|
* @param {FSUploader} Uploader
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step2_NoFilesFSUpload: function( Uploader ){
|
|
|
|
|
return Uploader.numberOfFiles > 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if any files haven't error.
|
|
|
|
|
* @param {FSUploader} Uploader
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step3_ErrorsFSUpload: function( Uploader ){
|
|
|
|
|
return !Uploader.hasErrors;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if all files are uploaded.
|
|
|
|
|
* @param {FSUploader} Uploader
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step4_AllFilesUploadedFSUpload: function( Uploader ){
|
|
|
|
|
return Uploader.allFilesUploaded;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one checkbox of romhacks modifications is checked.
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step5_RomhacksModificationsCheckboxes: function(){
|
|
|
|
|
return verifyCheckboxes( document.querySelector( '#modifications-group' ) );
|
|
|
|
|
},
|
|
|
|
|
|
2026-06-10 11:04:26 +02:00
|
|
|
step5_UtilitiesSystemsCheckboxes: function(){
|
|
|
|
|
return verifyCheckboxes( document.querySelector( '#systems-group' ) );
|
|
|
|
|
},
|
|
|
|
|
|
2026-05-20 18:25:15 +02:00
|
|
|
/**
|
|
|
|
|
* Verify if the description field has at least one character.
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step6_VerifyDescription: function(){
|
|
|
|
|
return verifyMDE('description');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if a game is provided.
|
|
|
|
|
* @param element this.$el
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step7_VerifyGame: function( element ){
|
|
|
|
|
|
2026-06-10 11:04:26 +02:00
|
|
|
const GAME_SELECTOR_MODE = document.querySelector('input[name="game_selection_mode"]')?.value ?? "game";
|
|
|
|
|
if( GAME_SELECTOR_MODE === 'platform' || GAME_SELECTOR_MODE === 'none' )
|
|
|
|
|
return true;
|
|
|
|
|
|
2026-05-20 18:25:15 +02:00
|
|
|
// Check if we have an already existent selected game.
|
|
|
|
|
const GAME_ID_INPUT = document.querySelector('input[name="game_id"]');
|
|
|
|
|
if( GAME_ID_INPUT ){
|
|
|
|
|
if( GAME_ID_INPUT.value !== '' && Number(GAME_ID_INPUT.value) > 0){
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we have a new game.
|
|
|
|
|
let gameSelector = element.querySelector('[x-data="GameSelector()"]');
|
|
|
|
|
gameSelector = gameSelector ? Alpine.$data(gameSelector) : null;
|
|
|
|
|
if( gameSelector !== null ){
|
|
|
|
|
|
|
|
|
|
if( !gameSelector.name || !gameSelector.name.toString().trim().length )
|
|
|
|
|
return false;
|
|
|
|
|
if( !gameSelector.platformId || gameSelector.platformId === '' || gameSelector.platformId === 0 )
|
|
|
|
|
return false;
|
|
|
|
|
if( !gameSelector.genreId || gameSelector.genreId === '' || gameSelector.genreId === 0 )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one checkbox of languages is checked.
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step8_LanguagesCheckboxes: function(){
|
|
|
|
|
return verifyCheckboxes( document.querySelector( '#languages-group' ) );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one (new) author has been filled.
|
|
|
|
|
* @return {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step9_verifyAuthors: function(){
|
|
|
|
|
const authorField = document.querySelectorAll('input[name="authors[]"]');
|
|
|
|
|
const newAuthorField = document.querySelectorAll('input[name="new-authors[]"]');
|
|
|
|
|
|
|
|
|
|
return ( authorField.length > 0 || newAuthorField.length > 0 );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if a main image has been uploaded.
|
|
|
|
|
* @param element this.$el
|
|
|
|
|
* @return {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step10_verifyMainImage: function( element ){
|
|
|
|
|
let MainImageData = element.querySelector('[x-data="MainImageManager()"]');
|
|
|
|
|
MainImageData = MainImageData ? Alpine.$data(MainImageData) : null;
|
|
|
|
|
|
|
|
|
|
if( ! MainImageData ){
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return MainImageData.uploaded;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify if at least one image is uploaded in the gallery.
|
|
|
|
|
* @param element this.$el
|
|
|
|
|
* @return {boolean}
|
|
|
|
|
*/
|
|
|
|
|
step11_verifyGallery: function( element){
|
|
|
|
|
let GalleryData = element.querySelector('[x-data="GalleryManager()"]');
|
|
|
|
|
GalleryData = GalleryData ? Alpine.$data(GalleryData) : null;
|
|
|
|
|
|
|
|
|
|
if( ! GalleryData ){
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GalleryData.number > 0 && GalleryData.allUploaded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle entire submission process.
|
|
|
|
|
*/
|
|
|
|
|
window.Submission = function(){
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If the script is during a try of submission process.
|
|
|
|
|
* @type {boolean}
|
|
|
|
|
*/
|
|
|
|
|
duringSubmissionProcess: false,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Error checked.
|
|
|
|
|
* @type {string|null}
|
|
|
|
|
*/
|
|
|
|
|
errorKey: null,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return error message.
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
get errorMessage(){
|
|
|
|
|
return ERROR_TABLE[this.errorKey] ?? "Unknown error";
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
init(){
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current FSUploader if initialized.
|
|
|
|
|
*
|
|
|
|
|
* @returns {FSUploader|null}
|
|
|
|
|
* @constructor
|
|
|
|
|
*/
|
|
|
|
|
get Uploader(){
|
|
|
|
|
const el = this.$el.querySelector('[x-data="FSUploader()"]');
|
|
|
|
|
return el ? Alpine.$data(el) : null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Do each form verifications.
|
|
|
|
|
* Update also this.errorKey.
|
|
|
|
|
*
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
verifyForm(){
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 1: During File upload" );
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step1_DuringFSUpload( this.Uploader ) ){
|
|
|
|
|
this.errorKey = "isUploading";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 2: No files uploaded" );
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step2_NoFilesFSUpload( this.Uploader ) ){
|
|
|
|
|
this.errorKey = "noFiles";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( 'Step 3: Error in file upload')
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step3_ErrorsFSUpload( this.Uploader ) ){
|
|
|
|
|
this.errorKey = "uploadError";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info("Step 4: All files uploaded");
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step4_AllFilesUploadedFSUpload( this.Uploader ) ){
|
|
|
|
|
this.errorKey = "notAllFilesDone";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 11:04:26 +02:00
|
|
|
if( SECTION() === "romhacks" || SECTION() === "lua-scripts" ){
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 5: Verify modifications")
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step5_RomhacksModificationsCheckboxes()){
|
|
|
|
|
this.errorKey = "noModifications";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-06-10 11:04:26 +02:00
|
|
|
} else if( SECTION() === "utilities" ){
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 5: Verify systems");
|
2026-06-10 11:04:26 +02:00
|
|
|
if( !SubmissionVerifications.step5_UtilitiesSystemsCheckboxes()){
|
|
|
|
|
this.errorKey = "noSystems";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-05-20 18:25:15 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 6: Verify description");
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step6_VerifyDescription() ){
|
|
|
|
|
this.errorKey = "noDescription";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 7: Verify game");
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step7_VerifyGame( this.$el ) ){
|
|
|
|
|
this.errorKey = "noGame";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info("Step 8: Verify languages");
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step8_LanguagesCheckboxes()){
|
|
|
|
|
this.errorKey = "noLanguages";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 9: Verify authors" );
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step9_verifyAuthors()){
|
|
|
|
|
this.errorKey = "noAuthors";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 10: Verify Main image" );
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step10_verifyMainImage( this.$el )){
|
|
|
|
|
this.errorKey = "noMainImage";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
console.info( "Step 11: Verify gallery images" );
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !SubmissionVerifications.step11_verifyGallery( this.$el )){
|
|
|
|
|
this.errorKey = "noGalleryImages";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scroll to the specific error field.
|
|
|
|
|
*/
|
|
|
|
|
scrollToError(){
|
|
|
|
|
const refMap = {
|
|
|
|
|
noFiles: 'uploadTarget',
|
|
|
|
|
isUploading: 'uploadTarget',
|
|
|
|
|
notAllFilesDone: 'uploadTarget',
|
|
|
|
|
uploadError: 'uploadTarget',
|
|
|
|
|
noModifications: 'modificationsGroup',
|
2026-06-10 11:04:26 +02:00
|
|
|
noSystems: 'systemsGroup',
|
2026-05-20 18:25:15 +02:00
|
|
|
noDescription: 'descriptionField',
|
|
|
|
|
noGame: 'gameSelector',
|
|
|
|
|
noLanguages: 'languagesGroup',
|
|
|
|
|
noAuthors: 'authorsSelector',
|
|
|
|
|
noMainImage: 'main-image-field',
|
|
|
|
|
noGalleryImages: 'gallery-field',
|
|
|
|
|
isSubmitting: 'submitButton'
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
const targetKey = refMap[this.errorKey];
|
|
|
|
|
|
|
|
|
|
const target = this.$refs[targetKey]
|
|
|
|
|
|| this.$el.querySelector(`[data-target="${targetKey}"]`)
|
|
|
|
|
|| this.$el.querySelector(`[x-ref="${targetKey}"]`)
|
|
|
|
|
|| this.$el.querySelector('.upload-list')
|
|
|
|
|
|| this.$el.querySelector('.form-upload');
|
2026-05-20 18:25:15 +02:00
|
|
|
|
|
|
|
|
if (target) {
|
|
|
|
|
target.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If you want to submit the form.
|
|
|
|
|
* @param {Event} e
|
|
|
|
|
*/
|
|
|
|
|
submitForm( e ){
|
|
|
|
|
|
|
|
|
|
if( this.duringSubmissionProcess )
|
|
|
|
|
return; // Don't submit two times.
|
|
|
|
|
|
|
|
|
|
this.errorKey = null; // Reset.
|
|
|
|
|
this.duringSubmissionProcess = true;
|
|
|
|
|
|
2026-06-08 16:25:52 +02:00
|
|
|
const STATE = document.querySelector('select[name="submit-state"]')?.value;
|
|
|
|
|
if( STATE === 'draft' ){
|
|
|
|
|
e.target.submit();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 18:25:15 +02:00
|
|
|
if( !this.verifyForm() ){
|
|
|
|
|
|
|
|
|
|
this.scrollToError();
|
|
|
|
|
|
|
|
|
|
this.duringSubmissionProcess = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.target.submit();
|
2026-06-16 16:21:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async requestFeatured( entryId ){
|
|
|
|
|
const csrf = CSRF();
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`/api/entry/${entryId}/featured`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': csrf
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const json = await response.json();
|
|
|
|
|
|
|
|
|
|
const entry_featured_button = document.querySelector('#entry-featured-button');
|
|
|
|
|
const entry_featured_body = document.querySelector('#entry-featured-body');
|
|
|
|
|
|
|
|
|
|
if( json.success ){
|
|
|
|
|
entry_featured_body.innerHTML = '<p>Request submitted</p>';
|
|
|
|
|
entry_featured_button.style.display = 'none';
|
|
|
|
|
} else {
|
|
|
|
|
entry_featured_body.innerHTML = '<p>Request failed. Please refresh the page and retry.</p>';
|
|
|
|
|
entry_featured_button.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 18:25:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|