From 8b9cfe1b1b22e85258d4d4d47c0f625a7355393f Mon Sep 17 00:00:00 2001 From: Kai Danielmeier Date: Mon, 19 Oct 2026 14:41:00 +0200 Subject: [PATCH 1/3] feat: Add Talk meeting button to calendar --- nextcloud/locales/de.json | 7 +++- nextcloud/locales/en.json | 7 +++- nextcloud/manifest.json | 21 +++++++++- nextcloud/src/index.js | 6 ++- nextcloud/src/lib/client.js | 27 ++++++++++++- nextcloud/src/ui/talkmeeting-button.js | 55 ++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 nextcloud/src/ui/talkmeeting-button.js diff --git a/nextcloud/locales/de.json b/nextcloud/locales/de.json index caf163e..5428be0 100644 --- a/nextcloud/locales/de.json +++ b/nextcloud/locales/de.json @@ -135,6 +135,11 @@ "setting.uploadFolder.help": "Ordner in Ihrer Nextcloud für Dateien, die aus dem Webmail hochgeladen werden. Wird bei Bedarf angelegt.", "setting.folderLayout": "Ordnerstruktur", "setting.folderLayout.help": "Wie hochgeladene Dateien im Upload-Ordner abgelegt werden.", + "setting.enableTalkMeetingsButton": "Aktiviere Talk Button", + "setting.enableTalkMeetingsButtonHelp": "Aktiviere Button zum hinzufügen eines Nextcloud Talk Meeting Raums in Kalendereinträgen", + "talkMeeting.add": "Talk Meeting hinzufügen", + "talkMeeting.added": "Talk Meeting hinzugefügt", + "talkMeeting.error": "Kann Talk Meeting nicht erstellen: {message}", "date": "Ein Ordner pro Tag", "flat": "Alles direkt im Upload-Ordner" -} +} \ No newline at end of file diff --git a/nextcloud/locales/en.json b/nextcloud/locales/en.json index 1b6e07e..7813a10 100644 --- a/nextcloud/locales/en.json +++ b/nextcloud/locales/en.json @@ -135,6 +135,11 @@ "setting.uploadFolder.help": "Folder in your Nextcloud for files uploaded from the webmail. Created when needed.", "setting.folderLayout": "Folder layout", "setting.folderLayout.help": "How uploads are sorted in the upload folder.", + "setting.enableTalkMeetingsButton": "Aktivate Talk button", + "setting.enableTalkMeetingsButtonHelp": "Aktivate button to add Nextcloud Talk meeting room in calendard entries", + "talkMeeting.add": "Add Talk meeting", + "talkMeeting.added": "Talk meeting added", + "talkMeeting.error": "Could not create Talk meeting: {message}", "date": "One folder per day", "flat": "All in the upload folder" -} +} \ No newline at end of file diff --git a/nextcloud/manifest.json b/nextcloud/manifest.json index 86adc95..80bd089 100644 --- a/nextcloud/manifest.json +++ b/nextcloud/manifest.json @@ -16,7 +16,10 @@ "ui:toolbar", "ui:app-page", "compose:open", - "ui:download-file" + "ui:download-file", + "calendar:read", + "calendar:write", + "ui:calendar-action" ], "entrypoint": "index.js", "minAppVersion": "1.15.0", @@ -84,6 +87,12 @@ "date", "flat" ] + }, + "enableTalkMeetingsButton": { + "type": "boolean", + "label": "setting.enableTalkMeetingsButton", + "description": "setting.enableTalkMeetingsButtonHelp", + "default": false } }, "locales": { @@ -784,6 +793,11 @@ "setting.uploadFolder.help": "Ordner in Ihrer Nextcloud für Dateien, die aus dem Webmail hochgeladen werden. Wird bei Bedarf angelegt.", "setting.folderLayout": "Ordnerstruktur", "setting.folderLayout.help": "Wie hochgeladene Dateien im Upload-Ordner abgelegt werden.", + "setting.enableTalkMeetingsButton": "Aktiviere Talk Button", + "setting.enableTalkMeetingsButtonHelp": "Aktiviere Button zum hinzufügen eines Nextcloud Talk Meeting Raums in Kalendereinträgen", + "talkMeeting.add": "Talk Meeting hinzufügen", + "talkMeeting.added": "Talk Meeting hinzugefügt", + "talkMeeting.error": "Kann Talk Meeting nicht erstellen: {message}", "date": "Ein Ordner pro Tag", "flat": "Alles direkt im Upload-Ordner" }, @@ -924,6 +938,11 @@ "setting.uploadFolder.help": "Folder in your Nextcloud for files uploaded from the webmail. Created when needed.", "setting.folderLayout": "Folder layout", "setting.folderLayout.help": "How uploads are sorted in the upload folder.", + "setting.enableTalkMeetingsButton": "Aktivate Talk button", + "setting.enableTalkMeetingsButtonHelp": "Aktivate button to add Nextcloud Talk meeting room in calendard entries", + "talkMeeting.add": "Add Talk meeting", + "talkMeeting.added": "Talk meeting added", + "talkMeeting.error": "Could not create Talk meeting: {message}", "date": "One folder per day", "flat": "All in the upload folder" }, diff --git a/nextcloud/src/index.js b/nextcloud/src/index.js index bc275a8..02f77c2 100644 --- a/nextcloud/src/index.js +++ b/nextcloud/src/index.js @@ -12,6 +12,7 @@ import { applyPickerResult } from './lib/picker-result.js'; import { SettingsSection } from './ui/settings-section.js'; import { PluginDialog } from './ui/dialog.js'; import { FilesPage } from './ui/files-page.js'; +import { AddTalkMeetingButton } from './ui/talkmeeting-button.js'; const FIRST_RUN_KEY = 'firstRunOffered'; @@ -44,8 +45,8 @@ export async function activate(api) { bg = api; setApi(api); await registerActions(api).catch((err) => api.log.warn('setActions failed', err)); - api.i18n.onLocaleChange(() => { void registerActions(api).catch(() => {}); }); - await offerFirstRun(api).catch(() => {}); + api.i18n.onLocaleChange(() => { void registerActions(api).catch(() => { }); }); + await offerFirstRun(api).catch(() => { }); } /** @@ -130,4 +131,5 @@ export const slots = { 'settings-section': { component: SettingsSection }, 'plugin-dialog': { component: PluginDialog }, 'app-page': { component: FilesPage }, + 'calendar-event-actions': { component: AddTalkMeetingButton }, }; diff --git a/nextcloud/src/lib/client.js b/nextcloud/src/lib/client.js index 7b30f2e..7ec7c20 100644 --- a/nextcloud/src/lib/client.js +++ b/nextcloud/src/lib/client.js @@ -129,7 +129,7 @@ export function createClient(api, conn) { const parts = []; let loaded = 0; try { - for (;;) { + for (; ;) { const { done, value } = await reader.read(); if (done) break; parts.push(value); @@ -190,6 +190,31 @@ export function createClient(api, conn) { return localPassword(); }, + async createTalkRoom(eventTitle, eventDescription) { + const data = await ocs( + api, + origin, + '/ocs/v2.php/apps/spreed/api/v4/room', + { + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify( + { + roomName: eventTitle, + description: eventDescription, + roomType: 3, + }), + }, + { kind: 'room' } + ); + + const token = data?.token; + if (!token) throw new Error("No token in response"); + + const url = `${origin}/call/${token}`; + + return url; + }, + /** Where to open a file in the Nextcloud web interface. */ webUrl(entry) { if (entry.fileId) return `${origin}/f/${encodeURIComponent(entry.fileId)}`; diff --git a/nextcloud/src/ui/talkmeeting-button.js b/nextcloud/src/ui/talkmeeting-button.js new file mode 100644 index 0000000..2734af5 --- /dev/null +++ b/nextcloud/src/ui/talkmeeting-button.js @@ -0,0 +1,55 @@ +import { createElement as h, useState } from 'react'; +import { Button } from '@plugin-host/ui'; +import { createClient } from '../lib/client'; +import { getConnection } from '../lib/connection'; +import { getApi } from '../lib/host'; + +export function AddTalkMeetingButton(props) { + // props contains eventData object {start, end, title, description, isAllDay, location, virtualLocation} + const [busy, setBusy] = useState(false); + const eventTitle = props?.eventData?.title || 'meeting'; + const eventDescription = props?.eventData?.description || ''; + const setVirtualLocation = props?.setVirtualLocation; + const api = getApi() + const t = api.i18n.t; + const enabled = api.plugin.settings.enableTalkMeetingsButton + + if (!enabled) { + return null + } + + let conn + let client + getConnection(api).then(connection => { + conn = connection + client = createClient(api, connection) + }) + + const virtualLocationSet = props.virtualLocation != null && props.virtualLocation != "" + + async function handleClick() { + if (busy) return; + setBusy(true); + try { + // api.http.post resolves to { ok, status, data }: the parsed body is in `data`. + const roomUrl = await client.createTalkRoom(eventTitle, eventDescription) + + if (typeof setVirtualLocation === 'function') { + // An RPC stub: await it so a host-side error surfaces here. + await setVirtualLocation(roomUrl); + api.toast.success(t('talkMeeting.added')); + } else { + // No host setter: show the URL so the user can still copy it. + await api.ui.alert({ title: t('talkMeeting.added'), message: url }); + } + } catch (err) { + console.error("Error creating talk meeting", err) + const message = err && err.message ? err.message : String(err); + api.toast.error(t('talkMeeting.error', { message })); + } finally { + setBusy(false); + } + } + + return h(Button, { variant: 'secondary', size: 'sm', icon: 'video', busy, onClick: handleClick, disabled: virtualLocationSet }, t('talkMeeting.add')); +} \ No newline at end of file From cd06097aa7e3e1378d4b714195845c877120e521 Mon Sep 17 00:00:00 2001 From: Kai Danielmeier Date: Mon, 19 Oct 2026 15:07:58 +0200 Subject: [PATCH 2/3] Code cleanup --- nextcloud/src/ui/talkmeeting-button.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nextcloud/src/ui/talkmeeting-button.js b/nextcloud/src/ui/talkmeeting-button.js index 2734af5..3691c71 100644 --- a/nextcloud/src/ui/talkmeeting-button.js +++ b/nextcloud/src/ui/talkmeeting-button.js @@ -18,10 +18,8 @@ export function AddTalkMeetingButton(props) { return null } - let conn let client getConnection(api).then(connection => { - conn = connection client = createClient(api, connection) }) @@ -31,15 +29,12 @@ export function AddTalkMeetingButton(props) { if (busy) return; setBusy(true); try { - // api.http.post resolves to { ok, status, data }: the parsed body is in `data`. const roomUrl = await client.createTalkRoom(eventTitle, eventDescription) if (typeof setVirtualLocation === 'function') { - // An RPC stub: await it so a host-side error surfaces here. await setVirtualLocation(roomUrl); api.toast.success(t('talkMeeting.added')); } else { - // No host setter: show the URL so the user can still copy it. await api.ui.alert({ title: t('talkMeeting.added'), message: url }); } } catch (err) { From 1abb30fd135bbe9002c4bdc7ab8758ae571fbc28 Mon Sep 17 00:00:00 2001 From: kdanielm Date: Wed, 28 Oct 2026 17:56:01 +0100 Subject: [PATCH 3/3] fix: Fix typos in manifest.json for Talk button settings Fixed typos in english translation --- nextcloud/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nextcloud/manifest.json b/nextcloud/manifest.json index 80bd089..edea5e8 100644 --- a/nextcloud/manifest.json +++ b/nextcloud/manifest.json @@ -938,8 +938,8 @@ "setting.uploadFolder.help": "Folder in your Nextcloud for files uploaded from the webmail. Created when needed.", "setting.folderLayout": "Folder layout", "setting.folderLayout.help": "How uploads are sorted in the upload folder.", - "setting.enableTalkMeetingsButton": "Aktivate Talk button", - "setting.enableTalkMeetingsButtonHelp": "Aktivate button to add Nextcloud Talk meeting room in calendard entries", + "setting.enableTalkMeetingsButton": "Activate Talk button", + "setting.enableTalkMeetingsButtonHelp": "Activate button to add Nextcloud Talk meeting room in calendar entries", "talkMeeting.add": "Add Talk meeting", "talkMeeting.added": "Talk meeting added", "talkMeeting.error": "Could not create Talk meeting: {message}",