Unenforced character count in frontend
Build LinkLog Development Image / development-image (push) Successful in 11s
Build LinkLog Development Image / development-image (push) Successful in 11s
This commit is contained in:
@@ -16,11 +16,14 @@
|
||||
const refetchTitleButton = document.getElementById('refetch-title-button');
|
||||
const submitButton = document.getElementById('submit-button');
|
||||
const submitStatus = document.getElementById('submit-status');
|
||||
const characterCount = document.getElementById('character-count');
|
||||
|
||||
const token = localStorage.getItem('linklogAccessToken');
|
||||
let availableTags = [];
|
||||
let selectedTags = new Set();
|
||||
let currentUser = null;
|
||||
let maxPostCharacters = 500;
|
||||
let mastodonPostPrefix = 'From my #LinkLog: ';
|
||||
|
||||
// Utility function to add status messages; splits on \n into real <br> line breaks without using innerHTML.
|
||||
function setStatus(statusEl, message, isError = false) {
|
||||
@@ -53,7 +56,7 @@
|
||||
.then((user) => {
|
||||
currentUser = user;
|
||||
entryForm.classList.remove('hidden');
|
||||
Promise.all([loadTags(), loadMastodonPublishing()]);
|
||||
Promise.all([loadTags(), loadMastodonPublishing(), loadConfig()]).then(updateCharacterCount);
|
||||
})
|
||||
.catch(() => {
|
||||
localStorage.removeItem('linklogAccessToken');
|
||||
@@ -61,6 +64,17 @@
|
||||
entryForm.classList.add('hidden');
|
||||
});
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/public/config');
|
||||
if (!response.ok) throw new Error('Could not load config');
|
||||
const config = await response.json();
|
||||
if (config.max_post_characters) maxPostCharacters = config.max_post_characters;
|
||||
} catch (error) {
|
||||
console.error('Could not load config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load available tags
|
||||
async function loadTags() {
|
||||
try {
|
||||
@@ -82,11 +96,63 @@
|
||||
const config = await response.json();
|
||||
mastodonEnabledCheckbox.checked = Boolean(config.configured);
|
||||
mastodonPublishing.classList.toggle('hidden', !config.configured);
|
||||
if (config.post_prefix) {
|
||||
mastodonPostPrefix = config.post_prefix;
|
||||
} else if (config.hashtag) {
|
||||
mastodonPostPrefix = `#${String(config.hashtag).trim().replace(/^#/, '')} `;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Could not load Mastodon configuration:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the selected checkbox tags plus any typed new tags, mirroring the submit-time logic.
|
||||
function collectTags() {
|
||||
const tags = Array.from(selectedTags);
|
||||
const newTags = newTagsInput.value.trim();
|
||||
if (newTags) {
|
||||
tags.push(...newTags.split(',').map((t) => t.trim()).filter(Boolean));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
// Format a UTC timestamp the same way the Mastodon plugin does, so the estimated post length matches the server.
|
||||
function formatMastodonTimestamp(date) {
|
||||
const months = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
return `${date.getUTCFullYear()} ${months[date.getUTCMonth()]} ${pad(date.getUTCDate())} - ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}`;
|
||||
}
|
||||
|
||||
// Estimate the assembled Mastodon post length, mirroring backend/app/services/plugin_manager.py.
|
||||
function estimatePostLength() {
|
||||
const title = titleInput.value.trim();
|
||||
const comment = commentInput.value.trim();
|
||||
const url = removeKnownTrackingParams(urlInput.value.trim());
|
||||
const parts = [mastodonPostPrefix.trim()];
|
||||
if (title) parts.push(title);
|
||||
if (comment) parts.push(comment);
|
||||
if (title) parts.push(`Logged on ${formatMastodonTimestamp(new Date())} UTC from: ${url}`);
|
||||
const tags = collectTags();
|
||||
if (tags.length) parts.push(tags.join(' '));
|
||||
return parts.join('\n\n').length;
|
||||
}
|
||||
|
||||
function updateCharacterCount() {
|
||||
const length = estimatePostLength();
|
||||
const overLimit = length > maxPostCharacters;
|
||||
characterCount.textContent = overLimit
|
||||
? `${length}/${maxPostCharacters} characters - exceeds the Mastodon post limit by ${length - maxPostCharacters}`
|
||||
: `${length}/${maxPostCharacters} characters`;
|
||||
characterCount.classList.toggle('over-limit', overLimit);
|
||||
}
|
||||
|
||||
[urlInput, titleInput, commentInput, newTagsInput].forEach((input) => {
|
||||
input.addEventListener('input', updateCharacterCount);
|
||||
});
|
||||
|
||||
// Render tag checkboxes
|
||||
function renderTags() {
|
||||
existingTagsEl.innerHTML = '';
|
||||
@@ -104,6 +170,7 @@
|
||||
} else {
|
||||
selectedTags.delete(tag);
|
||||
}
|
||||
updateCharacterCount();
|
||||
});
|
||||
|
||||
label.appendChild(checkbox);
|
||||
@@ -224,22 +291,13 @@
|
||||
const url = removeKnownTrackingParams(urlInput.value.trim());
|
||||
const title = titleInput.value.trim();
|
||||
const comment = commentInput.value.trim();
|
||||
const newTags = newTagsInput.value.trim();
|
||||
|
||||
if (!url || !title) {
|
||||
setStatus(submitStatus, 'URL and title are required', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Combine selected tags and new tags
|
||||
const tags = Array.from(selectedTags);
|
||||
if (newTags) {
|
||||
const newTagsList = newTags
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t);
|
||||
tags.push(...newTagsList);
|
||||
}
|
||||
const tags = collectTags();
|
||||
|
||||
submitButton.disabled = true;
|
||||
setStatus(submitStatus, 'Saving...', false);
|
||||
|
||||
Reference in New Issue
Block a user