diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 27e3437..6b1ab44 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -9,11 +9,13 @@ * Ability to minimize settings panels to only show headers in the Admin and Profile interfaces for easy navigation * Styled settings panel headers with distinct theme accent colors for visual distinction * Settings panels are collapsed by default when opening the Admin and Profile pages + * Show checked-by-default Mastodon publishing on New Entry only for configured users ### Fixed * Fixed element ID conflict on the Admin page so Available Themes load correctly * Positioned new-entry tag checkboxes after their label text * Kept new-entry tag checkboxes immediately left of their labels at all viewport sizes * Prevented the new-entry form grid from placing tag checkboxes above their labels + * Kept unconfigured New Entry Mastodon publishing controls hidden ## Version v0.1.1 ### Features diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index bc062d8..a45f6c8 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -1358,3 +1358,15 @@ The checkbox takes the full width of the grid which forces the label to be print ### Assistant outcome Fixed the actual layout conflict: `.form-section label` uses a grid layout whose selector overrode the tag label's flex layout. Added the more specific `.form-section label.tag-checkbox` inline-flex rule and reset its checkbox margin, keeping each checkbox directly left of its tag text. The focused API test module passed with 35 tests and `git diff --check` passed. + +### User +Only show the Mastodon publishing on new page if the user has Mastodon posting configured. Then default the checkbox to yes + +### Assistant outcome +Added a safe `configured` status to the authenticated Mastodon configuration response. New Entry now hides its Mastodon publishing section unless the current user has both an instance and access token configured; when shown, its checkbox defaults to checked. Link creation accepts `post_to_mastodon`, allowing an unchecked control to skip Mastodon while preserving dispatch of other plugins. Added posting and configuration regression coverage. + +### User +In the currently running contaier the user olaf has no mastodon configured but the mastodon checkbox is still visible + +### Assistant outcome +Found that `.form-section { display: grid; }` appeared later in the stylesheet than `.hidden { display: none; }`, overriding the initial hidden state of the Mastodon fieldset. Added `.form-section.hidden { display: none; }`, so the publishing control stays hidden until the authenticated configuration check explicitly reveals it. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index ac42c8b..fb5a14f 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -242,6 +242,8 @@ 223. On the new-entry page the checkbox should be following the labels - now they are positioned above. 224. The checkboxes should be immediately to the left of the label not below left 225. The checkbox takes the full width of the grid which forces the label to be printed underneath instead of directly next to it. +226. Only show the Mastodon publishing on new page if the user has Mastodon posting configured. Then default the checkbox to yes +227. In the currently running contaier the user olaf has no mastodon configured but the mastodon checkbox is still visible ## Future entries diff --git a/backend/app/api/links.py b/backend/app/api/links.py index 49cf0fc..40702c2 100644 --- a/backend/app/api/links.py +++ b/backend/app/api/links.py @@ -24,6 +24,7 @@ class LinkCreate(BaseModel): comment: str = '' timestamp: str | None = None tags: list[str] = [] + post_to_mastodon: bool = True class LinkUpdate(BaseModel): @@ -89,7 +90,11 @@ def create_link_endpoint(payload: LinkCreate, response: Response, authorization: raise HTTPException(status_code=422, detail=str(error)) from error if duplicate: response.status_code = status.HTTP_200_OK - plugin_results = plugin_manager.dispatch({'type': 'link_created', **record}) + plugin_results = plugin_manager.dispatch({ + 'type': 'link_created', + 'post_to_mastodon': payload.post_to_mastodon, + **record, + }) mastodon_result = next((result for result in plugin_results if result.get('plugin') == 'mastodon'), None) if mastodon_result and mastodon_result.get('status') == 'posted': mark_mastodon_posted(record['id'], info['user_id'], mastodon_result.get('post_id')) diff --git a/backend/app/api/user_config.py b/backend/app/api/user_config.py index cbf7fa3..7ff7812 100644 --- a/backend/app/api/user_config.py +++ b/backend/app/api/user_config.py @@ -390,6 +390,8 @@ def get_user_plugin_config(plugin_name: str, user: dict = Depends(get_current_us return {} config = json.loads(row['config']) if row['config'] else {} + if plugin_name == 'mastodon': + config['configured'] = bool(config.get('instance') and config.get('access_token')) if config.get('access_token'): config.pop('access_token') return config diff --git a/backend/app/services/plugin_manager.py b/backend/app/services/plugin_manager.py index 28b6280..da8957a 100644 --- a/backend/app/services/plugin_manager.py +++ b/backend/app/services/plugin_manager.py @@ -34,6 +34,9 @@ class MastodonPlugin(BasePlugin): return True def handle_event(self, event): + if not event.get('post_to_mastodon', True): + return {'status': 'skipped', 'plugin': self.name, 'reason': 'not_requested'} + config = dict(self.config) user_id = event.get('user_id') if user_id: diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 8be3506..b8f92f8 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -889,6 +889,18 @@ def test_link_submission_posts_to_enabled_mastodon_plugin(): server.server_close() +def test_link_submission_can_skip_mastodon_posting(): + with patch('backend.app.api.links.plugin_manager.dispatch', return_value=[]) as dispatch: + response = client.post('/api/links', headers=login_headers(), json={ + 'title': 'Private share', + 'url': 'https://example.com/private-share', + 'post_to_mastodon': False, + }) + + assert response.status_code == 201 + assert dispatch.call_args.args[0]['post_to_mastodon'] is False + + def test_mastodon_post_without_title_omits_source_line(): from backend.app.services.plugin_manager import MastodonPlugin @@ -922,6 +934,7 @@ def test_plugin_config_can_be_saved_for_mastodon(): payload = client.get('/api/user/plugins/mastodon', headers=headers).json() assert payload['instance'] == 'mastodon.social' assert payload['post_prefix'] == 'From my #LinkLog: ' + assert payload['configured'] is True admin_update = client.put('/api/admin/plugins/mastodon', headers=headers, json={ 'enabled': True, diff --git a/frontend/static/new-entry.js b/frontend/static/new-entry.js index 77ca954..7eb45ce 100644 --- a/frontend/static/new-entry.js +++ b/frontend/static/new-entry.js @@ -9,6 +9,7 @@ const commentInput = document.getElementById('comment-input'); const newTagsInput = document.getElementById('new-tags-input'); const existingTagsEl = document.getElementById('existing-tags'); + const mastodonPublishing = document.getElementById('mastodon-publishing'); const mastodonEnabledCheckbox = document.getElementById('mastodon-enabled'); const scrapeButton = document.getElementById('scrape-button'); const scrapeStatus = document.getElementById('scrape-status'); @@ -46,7 +47,7 @@ .then((user) => { currentUser = user; entryForm.classList.remove('hidden'); - loadTags(); + Promise.all([loadTags(), loadMastodonPublishing()]); }) .catch(() => { localStorage.removeItem('linklogAccessToken'); @@ -66,6 +67,20 @@ } } + async function loadMastodonPublishing() { + try { + const response = await fetch('/api/user/plugins/mastodon', { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!response.ok) return; + const config = await response.json(); + mastodonEnabledCheckbox.checked = Boolean(config.configured); + mastodonPublishing.classList.toggle('hidden', !config.configured); + } catch (error) { + console.error('Could not load Mastodon configuration:', error); + } + } + // Render tag checkboxes function renderTags() { existingTagsEl.innerHTML = ''; @@ -171,6 +186,7 @@ url, comment, tags, + post_to_mastodon: mastodonEnabledCheckbox.checked, }), }); diff --git a/frontend/static/style.css b/frontend/static/style.css index c005461..31b9539 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -1006,6 +1006,10 @@ button:disabled { gap: 8px; } +.form-section.hidden { + display: none; +} + .form-section label { display: grid; gap: 6px; diff --git a/frontend/templates/new-entry.html b/frontend/templates/new-entry.html index d61cbeb..1cd4a20 100644 --- a/frontend/templates/new-entry.html +++ b/frontend/templates/new-entry.html @@ -80,11 +80,11 @@ -