add shitpost mode config options
This commit is contained in:
@@ -86,6 +86,8 @@
|
|||||||
"web_meta_extraction": true,
|
"web_meta_extraction": true,
|
||||||
"bypass_duplicate_check": true,
|
"bypass_duplicate_check": true,
|
||||||
"shitpost_mode": false,
|
"shitpost_mode": false,
|
||||||
|
"shitpost_require_rating": false,
|
||||||
|
"shitpost_min_tags": 0,
|
||||||
"protect_files": false,
|
"protect_files": false,
|
||||||
"enable_dynamic_thumbs": false,
|
"enable_dynamic_thumbs": false,
|
||||||
"allowed_comment_images": [
|
"allowed_comment_images": [
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ window.initUploadForm = (selector) => {
|
|||||||
const commentMaxLen = (commentMaxLenAttr && commentMaxLenAttr !== 'null') ? parseInt(commentMaxLenAttr) : null;
|
const commentMaxLen = (commentMaxLenAttr && commentMaxLenAttr !== 'null') ? parseInt(commentMaxLenAttr) : null;
|
||||||
|
|
||||||
const isShitpost = form.classList.contains('shitpost-mode-active') || !!window.f0ckShitpostMode;
|
const isShitpost = form.classList.contains('shitpost-mode-active') || !!window.f0ckShitpostMode;
|
||||||
|
// Config-driven shitpost overrides
|
||||||
|
const shitpostRequireRating = isShitpost && !!window.f0ckShitpostRequireRating;
|
||||||
|
const shitpostMinTags = isShitpost ? (parseInt(window.f0ckShitpostMinTags) || 0) : 0;
|
||||||
let tags = [];
|
let tags = [];
|
||||||
let autoTags = []; // Track tags suggested from metadata
|
let autoTags = []; // Track tags suggested from metadata
|
||||||
let selectedFiles = []; // Array of files for shitpost_mode
|
let selectedFiles = []; // Array of files for shitpost_mode
|
||||||
@@ -548,14 +551,23 @@ window.initUploadForm = (selector) => {
|
|||||||
const isShitpost = !!window.f0ckShitpostMode;
|
const isShitpost = !!window.f0ckShitpostMode;
|
||||||
const rating = form.querySelector('input[name="rating"]:checked');
|
const rating = form.querySelector('input[name="rating"]:checked');
|
||||||
|
|
||||||
// In Shitpost Mode, ratings are per-item (optional) and tags are optional — just need files
|
// In Shitpost Mode, ratings are per-item. If require rating is true, every item must be rated.
|
||||||
const hasRating = (isShitpost && activeMode === 'file') ? true : (rating !== null);
|
let hasRating = true;
|
||||||
|
if (isShitpost && activeMode === 'file') {
|
||||||
|
if (shitpostRequireRating) {
|
||||||
|
hasRating = selectedFiles.length > 0 && selectedFiles.every(item => ['sfw', 'nsfw', 'nsfl'].includes(item.rating));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasRating = (rating !== null);
|
||||||
|
}
|
||||||
|
|
||||||
let hasTags = true;
|
let hasTags = true;
|
||||||
if (!isShitpost) {
|
if (!isShitpost) {
|
||||||
hasTags = tags.length >= minTags;
|
hasTags = tags.length >= minTags;
|
||||||
|
} else if (shitpostMinTags > 0 && activeMode === 'file') {
|
||||||
|
// In shitpost file mode with min-tags enforced: every queued item must meet the threshold.
|
||||||
|
hasTags = selectedFiles.length === 0 || selectedFiles.every(item => (item.tags || []).length >= shitpostMinTags);
|
||||||
}
|
}
|
||||||
// In shitpost file mode: hasTags is always true (untagged is allowed)
|
|
||||||
|
|
||||||
// Toggle visibility of global rating/comment/tag sections
|
// Toggle visibility of global rating/comment/tag sections
|
||||||
const ratingSec = form.querySelector('.global-rating-section');
|
const ratingSec = form.querySelector('.global-rating-section');
|
||||||
@@ -606,18 +618,27 @@ window.initUploadForm = (selector) => {
|
|||||||
? (ssrSelectFileText || i18n.select_file || 'Select a file')
|
? (ssrSelectFileText || i18n.select_file || 'Select a file')
|
||||||
: (i18n.enter_url || 'Enter a URL');
|
: (i18n.enter_url || 'Enter a URL');
|
||||||
} else if (!hasTags) {
|
} else if (!hasTags) {
|
||||||
// non-shitpost only
|
// non-shitpost or shitpost with min-tags
|
||||||
const remaining = minTags - tags.length;
|
if (isShitpost && shitpostMinTags > 0) {
|
||||||
const tpl = i18n.tags_required || '{n} more tag{s} required';
|
const remaining = shitpostMinTags - Math.min(...selectedFiles.map(item => (item.tags || []).length));
|
||||||
btnText.textContent = tpl
|
btnText.textContent = `${remaining} more tag${remaining !== 1 ? 's' : ''} required per item`;
|
||||||
.replace('{n}', remaining)
|
} else {
|
||||||
.replace('{s}', remaining !== 1 ? 's' : '');
|
const remaining = minTags - tags.length;
|
||||||
|
const tpl = i18n.tags_required || '{n} more tag{s} required';
|
||||||
|
btnText.textContent = tpl
|
||||||
|
.replace('{n}', remaining)
|
||||||
|
.replace('{s}', remaining !== 1 ? 's' : '');
|
||||||
|
}
|
||||||
} else if (!hasRating) {
|
} else if (!hasRating) {
|
||||||
const nsflEnabled = !!form.querySelector('input[name="rating"][value="nsfl"]');
|
const nsflEnabled = !!form.querySelector('input[name="rating"][value="nsfl"]');
|
||||||
if (nsflEnabled) {
|
if (isShitpost && shitpostRequireRating) {
|
||||||
btnText.textContent = i18n.select_rating_nsfl || 'Select SFW, NSFW or NSFL';
|
btnText.textContent = 'Select a rating for each item';
|
||||||
} else {
|
} else {
|
||||||
btnText.textContent = i18n.select_rating || 'Select SFW or NSFW';
|
if (nsflEnabled) {
|
||||||
|
btnText.textContent = i18n.select_rating_nsfl || 'Select SFW, NSFW or NSFL';
|
||||||
|
} else {
|
||||||
|
btnText.textContent = i18n.select_rating || 'Select SFW or NSFW';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (activeMode === 'url' && urlInput && ytRegex.test(urlInput.value.trim()) && window.f0ckEnableYoutubeUpload !== false) {
|
if (activeMode === 'url' && urlInput && ytRegex.test(urlInput.value.trim()) && window.f0ckEnableYoutubeUpload !== false) {
|
||||||
@@ -841,19 +862,21 @@ window.initUploadForm = (selector) => {
|
|||||||
let commentUI = '';
|
let commentUI = '';
|
||||||
if (isShitpost) {
|
if (isShitpost) {
|
||||||
const nsflEnabled = !!form.querySelector('input[name="rating"][value="nsfl"]');
|
const nsflEnabled = !!form.querySelector('input[name="rating"][value="nsfl"]');
|
||||||
|
// Build per-item rating HTML
|
||||||
|
const ratingValue = item.rating;
|
||||||
ratingSwitch = `
|
ratingSwitch = `
|
||||||
<div class="item-rating-container">
|
<div class="item-rating-container">
|
||||||
<label class="item-rating-option">
|
<label class="item-rating-option">
|
||||||
<input type="radio" name="rating_${index}" value="sfw" ${item.rating === 'sfw' ? 'checked' : ''}>
|
<input type="radio" name="rating_${index}" value="sfw" ${ratingValue === 'sfw' ? 'checked' : ''}>
|
||||||
<span class="item-rating-label sfw">SFW</span>
|
<span class="item-rating-label sfw">SFW</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="item-rating-option">
|
<label class="item-rating-option">
|
||||||
<input type="radio" name="rating_${index}" value="nsfw" ${item.rating === 'nsfw' ? 'checked' : ''}>
|
<input type="radio" name="rating_${index}" value="nsfw" ${ratingValue === 'nsfw' ? 'checked' : ''}>
|
||||||
<span class="item-rating-label nsfw">NSFW</span>
|
<span class="item-rating-label nsfw">NSFW</span>
|
||||||
</label>
|
</label>
|
||||||
${nsflEnabled ? `
|
${nsflEnabled ? `
|
||||||
<label class="item-rating-option">
|
<label class="item-rating-option">
|
||||||
<input type="radio" name="rating_${index}" value="nsfl" ${item.rating === 'nsfl' ? 'checked' : ''}>
|
<input type="radio" name="rating_${index}" value="nsfl" ${ratingValue === 'nsfl' ? 'checked' : ''}>
|
||||||
<span class="item-rating-label nsfl">NSFL</span>
|
<span class="item-rating-label nsfl">NSFL</span>
|
||||||
</label>
|
</label>
|
||||||
` : ''}
|
` : ''}
|
||||||
@@ -861,10 +884,11 @@ window.initUploadForm = (selector) => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const tagsPlaceholder = window.f0ckI18n?.upload_tags_placeholder || 'Tags...';
|
const tagsPlaceholder = window.f0ckI18n?.upload_tags_placeholder || 'Tags...';
|
||||||
|
const minTagsHint = shitpostMinTags > 0 ? ` (min ${shitpostMinTags})` : '';
|
||||||
tagsUI = `
|
tagsUI = `
|
||||||
<div class="item-tags-container">
|
<div class="item-tags-container">
|
||||||
<div class="item-tags-list"></div>
|
<div class="item-tags-list"></div>
|
||||||
<input type="text" class="item-tag-input" placeholder="${window.escapeHtmlUpload(tagsPlaceholder)}" enterkeyhint="done">
|
<input type="text" class="item-tag-input" placeholder="${window.escapeHtmlUpload(tagsPlaceholder + minTagsHint)}" enterkeyhint="done">
|
||||||
<div class="tag-suggestions" style="display:none;"></div>
|
<div class="tag-suggestions" style="display:none;"></div>
|
||||||
<div class="item-meta-suggestions" style="display:none; margin-top:5px; font-size:0.7rem; opacity:0.6;"></div>
|
<div class="item-meta-suggestions" style="display:none; margin-top:5px; font-size:0.7rem; opacity:0.6;"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -901,7 +925,10 @@ window.initUploadForm = (selector) => {
|
|||||||
if (isShitpost) {
|
if (isShitpost) {
|
||||||
// Handle Rating
|
// Handle Rating
|
||||||
infoRow.querySelectorAll('.item-rating-option input').forEach(radio => {
|
infoRow.querySelectorAll('.item-rating-option input').forEach(radio => {
|
||||||
radio.onchange = () => { item.rating = radio.value; };
|
radio.onchange = () => {
|
||||||
|
item.rating = radio.value;
|
||||||
|
updateSubmitButton();
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle Comment
|
// Handle Comment
|
||||||
|
|||||||
@@ -1077,6 +1077,8 @@ process.on('uncaughtException', err => {
|
|||||||
registration_web_toggle_enabled: cfg.websrv.open_registration_web_toggle !== false,
|
registration_web_toggle_enabled: cfg.websrv.open_registration_web_toggle !== false,
|
||||||
get trusted_uploads() { return getTrustedUploads(); },
|
get trusted_uploads() { return getTrustedUploads(); },
|
||||||
get shitpost_mode() { return getShitpostMode(); },
|
get shitpost_mode() { return getShitpostMode(); },
|
||||||
|
shitpost_require_rating: !!cfg.websrv.shitpost_require_rating,
|
||||||
|
shitpost_min_tags: parseInt(cfg.websrv.shitpost_min_tags) || 0,
|
||||||
get about_text() { return getAboutText(); },
|
get about_text() { return getAboutText(); },
|
||||||
get rules_text() { return getRulesText(); },
|
get rules_text() { return getRulesText(); },
|
||||||
get terms_text() { return getTermsText(); },
|
get terms_text() { return getTermsText(); },
|
||||||
|
|||||||
@@ -119,23 +119,32 @@ export const handleUpload = async (req, res, self) => {
|
|||||||
return sendJson(res, { success: false, msg: 'No file provided' }, 400);
|
return sendJson(res, { success: false, msg: 'No file provided' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In shitpost mode, rating is optional — null means no rating tag is assigned (truly untagged)
|
// In shitpost mode, rating is optional — null means no rating tag is assigned (truly untagged).
|
||||||
const effectiveRating = (rating && ['sfw', 'nsfw', 'nsfl'].includes(rating)) ? rating : (is_shitpost ? null : null);
|
// If shitpost_require_rating is configured to true, a rating is strictly required.
|
||||||
|
const effectiveRating = (rating && ['sfw', 'nsfw', 'nsfl'].includes(rating)) ? rating : null;
|
||||||
|
|
||||||
if (!is_shitpost && !effectiveRating) {
|
if (!is_shitpost && !effectiveRating) {
|
||||||
return sendJson(res, { success: false, msg: 'Rating (sfw/nsfw/nsfl) is required' }, 400);
|
return sendJson(res, { success: false, msg: 'Rating (sfw/nsfw/nsfl) is required' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_shitpost && cfg.websrv.shitpost_require_rating === true && !effectiveRating) {
|
||||||
|
return sendJson(res, { success: false, msg: 'Rating (sfw/nsfw/nsfl) is required for each item' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
if (effectiveRating === 'nsfl' && !cfg.enable_nsfl) {
|
if (effectiveRating === 'nsfl' && !cfg.enable_nsfl) {
|
||||||
return sendJson(res, { success: false, msg: 'NSFL mode is currently disabled' }, 400);
|
return sendJson(res, { success: false, msg: 'NSFL mode is currently disabled' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(t => t.length > 0 && !['sfw', 'nsfw', 'nsfl'].includes(t.toLowerCase())) : [];
|
const tags = tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(t => t.length > 0 && !['sfw', 'nsfw', 'nsfl'].includes(t.toLowerCase())) : [];
|
||||||
const minTags = getMinTags();
|
const minTags = getMinTags();
|
||||||
// In shitpost mode, tags are optional — items without tags enter as untagged
|
// In shitpost mode, tags are optional by default — unless shitpost_min_tags is configured.
|
||||||
|
const shitpostMinTags = is_shitpost ? (parseInt(cfg.websrv.shitpost_min_tags) || 0) : 0;
|
||||||
if (!is_shitpost && tags.length < minTags) {
|
if (!is_shitpost && tags.length < minTags) {
|
||||||
return sendJson(res, { success: false, msg: `At least ${minTags} tags are required` }, 400);
|
return sendJson(res, { success: false, msg: `At least ${minTags} tags are required` }, 400);
|
||||||
}
|
}
|
||||||
|
if (is_shitpost && shitpostMinTags > 0 && tags.length < shitpostMinTags) {
|
||||||
|
return sendJson(res, { success: false, msg: `At least ${shitpostMinTags} tag${shitpostMinTags !== 1 ? 's' : ''} are required` }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
// Validate MIME type
|
// Validate MIME type
|
||||||
const allowedMimes = Object.keys(cfg.mimes);
|
const allowedMimes = Object.keys(cfg.mimes);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<link rel="stylesheet" href="/s/css/upload.css?v={{ ts }}">
|
<link rel="stylesheet" href="/s/css/upload.css?v={{ ts }}">
|
||||||
@endif
|
@endif
|
||||||
<script>window.f0ckThemes = {{ themes_json }}; window.f0ckDefaultTheme = "{{ default_theme }}"; window.f0ckDomain = "{{ domain }}"; window.f0ckGitHash = "{{ git_hash }}"; window.f0ckAllowedImages = {{ allowed_comment_images_json }}; window.f0ckEmbedYoutubeInComments = {{ embed_youtube_in_comments ? 'true' : 'false' }}; window.f0ckEnableYoutubeUpload = {{ enable_youtube_upload ? 'true' : 'false' }}; window.f0ckBrandImages = {{ custom_brand_images_json }}; window.f0ckMediaBase = "{{ paths_images }}"; window.f0ckShitpostMode = {{ shitpost_mode ? 'true' : 'false' }};</script>
|
<script>window.f0ckThemes = {{ themes_json }}; window.f0ckDefaultTheme = "{{ default_theme }}"; window.f0ckDomain = "{{ domain }}"; window.f0ckGitHash = "{{ git_hash }}"; window.f0ckAllowedImages = {{ allowed_comment_images_json }}; window.f0ckEmbedYoutubeInComments = {{ embed_youtube_in_comments ? 'true' : 'false' }}; window.f0ckEnableYoutubeUpload = {{ enable_youtube_upload ? 'true' : 'false' }}; window.f0ckBrandImages = {{ custom_brand_images_json }}; window.f0ckMediaBase = "{{ paths_images }}"; window.f0ckShitpostMode = {{ shitpost_mode ? 'true' : 'false' }}; window.f0ckShitpostRequireRating = {{ shitpost_require_rating ? 'true' : 'false' }}; window.f0ckShitpostMinTags = {{ shitpost_min_tags || 0 }};</script>
|
||||||
@if(!private_society || session)
|
@if(!private_society || session)
|
||||||
<script src="/s/js/marked.min.js" defer></script>
|
<script src="/s/js/marked.min.js" defer></script>
|
||||||
<script src="/s/js/comments.js?v={{ ts }}" defer></script>
|
<script src="/s/js/comments.js?v={{ ts }}" defer></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user