Character count in firefox plugin
Build LinkLog Development Image / development-image (push) Successful in 8s
Build LinkLog Development Image / development-image (push) Successful in 8s
This commit is contained in:
@@ -53,5 +53,7 @@
|
||||
"loginFailed": {"message": "Anmeldung fehlgeschlagen"},
|
||||
"loggedInSuccessfully": {"message": "Erfolgreich angemeldet"},
|
||||
"unableToLogIn": {"message": "Anmeldung nicht möglich. Überprüfe Backend-URL und Zugangsdaten."},
|
||||
"signedOut": {"message": "Abgemeldet"}
|
||||
"signedOut": {"message": "Abgemeldet"},
|
||||
"characterCount": {"message": "$USED$/$LIMIT$ Zeichen", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}}},
|
||||
"characterCountOverLimit": {"message": "$USED$/$LIMIT$ Zeichen - überschreitet das Mastodon-Beitragslimit um $OVER$", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}, "over": {"content": "$3"}}}
|
||||
}
|
||||
|
||||
@@ -181,5 +181,30 @@
|
||||
},
|
||||
"signedOut": {
|
||||
"message": "Signed out"
|
||||
},
|
||||
"characterCount": {
|
||||
"message": "$USED$/$LIMIT$ characters",
|
||||
"placeholders": {
|
||||
"used": {
|
||||
"content": "$1"
|
||||
},
|
||||
"limit": {
|
||||
"content": "$2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"characterCountOverLimit": {
|
||||
"message": "$USED$/$LIMIT$ characters - exceeds the Mastodon post limit by $OVER$",
|
||||
"placeholders": {
|
||||
"used": {
|
||||
"content": "$1"
|
||||
},
|
||||
"limit": {
|
||||
"content": "$2"
|
||||
},
|
||||
"over": {
|
||||
"content": "$3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,7 @@
|
||||
"loginFailed": {"message": "Error de inicio de sesión"},
|
||||
"loggedInSuccessfully": {"message": "Sesión iniciada correctamente"},
|
||||
"unableToLogIn": {"message": "No se pudo iniciar sesión. Comprueba la URL y las credenciales."},
|
||||
"signedOut": {"message": "Sesión cerrada"}
|
||||
"signedOut": {"message": "Sesión cerrada"},
|
||||
"characterCount": {"message": "$USED$/$LIMIT$ caracteres", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}}},
|
||||
"characterCountOverLimit": {"message": "$USED$/$LIMIT$ caracteres: supera el límite de publicación de Mastodon en $OVER$", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}, "over": {"content": "$3"}}}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,7 @@
|
||||
"loginFailed": {"message": "Échec de la connexion"},
|
||||
"loggedInSuccessfully": {"message": "Connexion réussie"},
|
||||
"unableToLogIn": {"message": "Connexion impossible. Vérifiez l’URL du serveur et vos identifiants."},
|
||||
"signedOut": {"message": "Déconnecté"}
|
||||
"signedOut": {"message": "Déconnecté"},
|
||||
"characterCount": {"message": "$USED$/$LIMIT$ caractères", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}}},
|
||||
"characterCountOverLimit": {"message": "$USED$/$LIMIT$ caractères : dépasse la limite de publication Mastodon de $OVER$", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}, "over": {"content": "$3"}}}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,7 @@
|
||||
"loginFailed": {"message": "Inloggen mislukt"},
|
||||
"loggedInSuccessfully": {"message": "Succesvol ingelogd"},
|
||||
"unableToLogIn": {"message": "Inloggen mislukt. Controleer de backend-URL en inloggegevens."},
|
||||
"signedOut": {"message": "Uitgelogd"}
|
||||
"signedOut": {"message": "Uitgelogd"},
|
||||
"characterCount": {"message": "$USED$/$LIMIT$ tekens", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}}},
|
||||
"characterCountOverLimit": {"message": "$USED$/$LIMIT$ tekens - overschrijdt de Mastodon-postlimiet met $OVER$", "placeholders": {"used": {"content": "$1"}, "limit": {"content": "$2"}, "over": {"content": "$3"}}}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_extensionName__",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "__MSG_extensionDescription__",
|
||||
"default_locale": "en-US",
|
||||
"permissions": [
|
||||
|
||||
@@ -149,6 +149,18 @@ button {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
margin: -6px 0 10px;
|
||||
color: #a6adc8;
|
||||
font-size: 0.75rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.char-count.over-limit {
|
||||
color: #f38ba8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auth-session {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #585b70;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<span data-i18n="commentLabel">Comment</span>
|
||||
<textarea id="comment" name="comment" rows="3" data-i18n-placeholder="commentPlaceholder" placeholder="Your comment"></textarea>
|
||||
</label>
|
||||
<div id="character-count" class="char-count" aria-live="polite"></div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n="tagsLabel">Tags</legend>
|
||||
|
||||
+82
-1
@@ -14,10 +14,14 @@ const authWarning = document.getElementById('auth-warning');
|
||||
const warningSettingsButton = document.getElementById('warning-settings');
|
||||
const authSession = document.getElementById('auth-session');
|
||||
const refetchTitleButton = document.getElementById('refetch-title');
|
||||
const characterCountEl = document.getElementById('character-count');
|
||||
const sessionStore = browser.storage.session;
|
||||
|
||||
const t = window.linklogI18n;
|
||||
|
||||
let maxPostCharacters = 500;
|
||||
let mastodonPostPrefix = 'From my #LinkLog: ';
|
||||
|
||||
function normalizeBackendOrigin(backendUrl) {
|
||||
try {
|
||||
const url = new URL(backendUrl);
|
||||
@@ -192,9 +196,11 @@ async function loadExistingTags() {
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.value = tag;
|
||||
checkbox.addEventListener('change', updateCharacterCount);
|
||||
label.append(checkbox, document.createTextNode(` ${tag}`));
|
||||
return label;
|
||||
}));
|
||||
updateCharacterCount();
|
||||
}
|
||||
|
||||
function getTags() {
|
||||
@@ -203,6 +209,79 @@ function getTags() {
|
||||
return [...new Set([...selected, ...newTags])].slice(0, 10);
|
||||
}
|
||||
|
||||
// Load public app settings (e.g. the Mastodon post length limit) once a backend is configured.
|
||||
async function loadConfig() {
|
||||
const settings = await getSettings();
|
||||
if (!settings.backendUrl || !(await hasBackendPermission(settings.backendUrl))) return;
|
||||
try {
|
||||
const response = await fetch(`${settings.backendUrl}/api/public/config`);
|
||||
if (!response.ok) return;
|
||||
const config = await response.json();
|
||||
if (config.max_post_characters) maxPostCharacters = config.max_post_characters;
|
||||
} catch (error) {
|
||||
// Character-count warning is advisory; ignore config load failures.
|
||||
}
|
||||
updateCharacterCount();
|
||||
}
|
||||
|
||||
// Load the user's configured Mastodon post prefix, so the estimated post length matches the server.
|
||||
async function loadMastodonPrefix() {
|
||||
const settings = await getSettings();
|
||||
if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl))) return;
|
||||
try {
|
||||
const response = await fetch(`${settings.backendUrl}/api/user/plugins/mastodon`, {
|
||||
headers: {'Authorization': `Bearer ${settings.accessToken}`},
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const config = await response.json();
|
||||
if (config.post_prefix) {
|
||||
mastodonPostPrefix = config.post_prefix;
|
||||
} else if (config.hashtag) {
|
||||
mastodonPostPrefix = `#${String(config.hashtag).trim().replace(/^#/, '')} `;
|
||||
}
|
||||
} catch (error) {
|
||||
// Character-count warning is advisory; ignore config load failures.
|
||||
}
|
||||
updateCharacterCount();
|
||||
}
|
||||
|
||||
// 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 = getTags();
|
||||
if (tags.length) parts.push(tags.join(' '));
|
||||
return parts.join('\n\n').length;
|
||||
}
|
||||
|
||||
function updateCharacterCount() {
|
||||
const length = estimatePostLength();
|
||||
const overLimit = length > maxPostCharacters;
|
||||
characterCountEl.textContent = overLimit
|
||||
? t('characterCountOverLimit', [String(length), String(maxPostCharacters), String(length - maxPostCharacters)])
|
||||
: t('characterCount', [String(length), String(maxPostCharacters)]);
|
||||
characterCountEl.classList.toggle('over-limit', overLimit);
|
||||
}
|
||||
|
||||
[titleInput, urlInput, commentInput, newTagsInput].forEach((input) => {
|
||||
input.addEventListener('input', updateCharacterCount);
|
||||
});
|
||||
|
||||
function removeKnownTrackingParams(urlString) {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
@@ -361,6 +440,8 @@ refetchTitleButton.addEventListener('click', (e) => {
|
||||
titleInput.value = '';
|
||||
fetchTitle(url, { force: true });
|
||||
});
|
||||
populateCurrentTab().then(checkExistingLink);
|
||||
populateCurrentTab().then(checkExistingLink).then(updateCharacterCount);
|
||||
loadExistingTags();
|
||||
updateFeedLink();
|
||||
loadConfig();
|
||||
loadMastodonPrefix();
|
||||
|
||||
Reference in New Issue
Block a user