feat(calendar): en læst Google-kalender, med sit eget endpoint og sin egen sweep
Samme registrering som Gmail, et andet scope og en anden redirect-URI. Google udsteder en refresh-token pr. godkendelse og holder op til hundrede i live pr. konto, hver med sine egne scopes — så en forbundet kalender rører ikke en Gmail, der virker, og at afbryde den ene tager ikke den anden med. Callbacken har sin egen sti. Én callback, der ud fra gemt tilstand afgjorde, hvilket af de to flows der kom tilbage, ville lægge valget af *hvilket token-lager der skrives* bag et opslag — og går det galt, går det galt ved at overskrive en virkende Gmail-forbindelse med kalender-tokens. Begivenhederne caches i et vindue på 90 dage tilbage og et år frem. Et månedsgitter bladrer, og et kald til Google pr. piletast er en kalender, der er langsommere end den kalender, den viser. Vinduet har en kant, og endpointet siger hvor den er: et tomt svar uden forklaring er en kalender, der lydløst påstår, at en dag er fri.
This commit is contained in:
@@ -61,6 +61,10 @@ const MESSAGES = {
|
||||
// Same shape for a mailbox read over IMAP: quoted when the host said something,
|
||||
// this when it did not.
|
||||
mailbox_failed: 'error.mailboxRefused',
|
||||
// And for a calendar read from Google. Its own code, because the sentence this
|
||||
// is wrapped in names where it came from — and "Gmail said no" about a calendar
|
||||
// sends somebody to the wrong panel.
|
||||
calendar_failed: 'error.calendarRefused',
|
||||
last_admin: 'error.lastAdmin'
|
||||
};
|
||||
|
||||
@@ -84,6 +88,9 @@ export function humanMessage(err) {
|
||||
if (err.code === 'gmail_failed' && err.message) {
|
||||
return t('error.gmailSaid', { what: err.message });
|
||||
}
|
||||
if (err.code === 'calendar_failed' && err.message) {
|
||||
return t('error.calendarSaid', { what: err.message });
|
||||
}
|
||||
|
||||
const key = MESSAGES[err.code];
|
||||
if (key) return t(key);
|
||||
@@ -361,6 +368,16 @@ export const api = {
|
||||
authorizeGmail: () => post('/gmail/authorize'),
|
||||
syncGmail: () => post('/gmail/sync'),
|
||||
|
||||
// The Google Calendar connection. Read-only: there is nothing here that writes
|
||||
// an event, only which calendars to look at.
|
||||
getCalendar: () => get('/calendar'),
|
||||
setCalendars: (shown) => put('/calendar/calendars', { shown }),
|
||||
disconnectCalendar: () => del('/calendar'),
|
||||
authorizeCalendar: () => post('/calendar/authorize'),
|
||||
syncCalendar: () => post('/calendar/sync'),
|
||||
calendarEvents: (from, to) =>
|
||||
get(`/calendar/events?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`),
|
||||
|
||||
// Notes. Listing and searching are one endpoint because they answer the same
|
||||
// question with different words.
|
||||
notes: (params = {}) => {
|
||||
|
||||
@@ -65,6 +65,7 @@ export const da = {
|
||||
'nav.upcoming': 'Kommende',
|
||||
'nav.delegated': 'Venter på andre',
|
||||
'nav.inbox': 'Indbakke',
|
||||
'nav.calendar': 'Kalender',
|
||||
'nav.settings': 'Indstillinger',
|
||||
'nav.signOut': 'Log ud',
|
||||
'nav.main': 'Hovedmenu',
|
||||
@@ -586,6 +587,30 @@ export const da = {
|
||||
'int.server': 'Server',
|
||||
'int.caldavAuth':
|
||||
'Brugernavn er din e-mail. Adgangskoden er en API-token — ikke din rigtige adgangskode.',
|
||||
'int.calendar': 'Google Kalender',
|
||||
'int.calendarHint':
|
||||
'Læses, aldrig skrives. Begivenhederne vises i Kalender ved siden af dine egne opgaver med forfaldsdato — de kan ikke flyttes eller lukkes herfra, for de hører til i Google.',
|
||||
'int.connectCalendar': 'Forbind Google Kalender',
|
||||
'int.calendarConnectedAs': 'Forbundet som',
|
||||
'int.whichCalendars': 'Hvilke kalendere vises',
|
||||
'int.noCalendars': 'Kontoen har ingen kalendere at vise.',
|
||||
'int.calendarLastSync': 'Sidst hentet {when}.',
|
||||
'int.calendarNeverSynced': 'Ikke hentet endnu.',
|
||||
'int.calendarFetchedOne': '{n} begivenhed hentet.',
|
||||
'int.calendarFetchedMany': '{n} begivenheder hentet.',
|
||||
'int.calendarNothingNew': 'Ingen begivenheder i vinduet.',
|
||||
'int.calendarConnected': 'Google Kalender er forbundet.',
|
||||
'int.calendarState':
|
||||
'Svaret fra Google passede ikke til det forsøg, der blev startet her. Prøv igen.',
|
||||
'int.calendarExpired': 'Forsøget udløb undervejs. Prøv igen.',
|
||||
'int.calendarInvalid': 'Svaret fra Google kunne ikke læses. Prøv igen.',
|
||||
'int.calendarFailed': 'Google afviste ombytningen af koden.',
|
||||
'int.calendarNoRefresh':
|
||||
'Google sendte ingen refresh-token. Fjern verdande under din Google-konto og forbind igen.',
|
||||
'int.calendarDenied': 'Adgangen blev afvist hos Google.',
|
||||
'int.calendarOrgInternal':
|
||||
'Google-appen er registreret som Internal og tager kun imod konti i organisationen. En privat konto kan ikke godkende den, uanset hvad der står på skærmen.',
|
||||
'int.calendarOther': 'Google svarede: {what}',
|
||||
'int.gmailFetchedOne': '{n} opgave hentet.',
|
||||
'int.gmailFetchedMany': '{n} opgaver hentet.',
|
||||
'int.gmailNothingNew': 'Ingen nye beskeder.',
|
||||
@@ -600,6 +625,17 @@ export const da = {
|
||||
'int.gmailDenied': 'Adgangen blev afvist hos Google.',
|
||||
'int.gmailOther': 'Gmail svarede: {what}',
|
||||
|
||||
// --- kalenderen, med Googles begivenheder lagt over ---------------------------------
|
||||
'cal.title': 'Kalender',
|
||||
'cal.notConnected':
|
||||
'Ingen kalender er forbundet endnu. Det gøres under Indstillinger → Integrationer.',
|
||||
'cal.settings': 'Indstillinger',
|
||||
'cal.allDay': 'Hele dagen',
|
||||
'cal.openInGoogle': 'Åbn i Google Kalender',
|
||||
'cal.beyondWindow':
|
||||
'Begivenheder gemmes fra {from} til {to}. Uden for det viser gitteret kun dine egne opgaver.',
|
||||
'cal.eventCannotMove': '{name} — en begivenhed i {calendar}. Den kan ikke flyttes herfra.',
|
||||
|
||||
// --- a project ----------------------------------------------------------------------
|
||||
'project.name': 'Projektets navn',
|
||||
'project.clickToRename': 'Klik for at omdøbe',
|
||||
@@ -741,6 +777,10 @@ export const da = {
|
||||
'Denne instans kan ikke genstarte sig selv: {missing} er ikke sat. En container kan ikke udskifte sit eget image — panelet gør arbejdet, og det her er kun måden at bede om det.',
|
||||
|
||||
'error.gmailSaid': 'Gmail svarede: {what}',
|
||||
'error.calendarRefused': 'Google afviste kaldet. Prøv at forbinde kalenderen igen.',
|
||||
'error.calendarSaid': 'Google svarede: {what}',
|
||||
'int.disconnectCalendarQuestion':
|
||||
'Afbryd forbindelsen til Google Kalender? Tokens bliver glemt, og den hentede kopi af begivenhederne bliver slettet. Dine egne opgaver rører den ikke.',
|
||||
|
||||
'error.mailboxRefused': 'Postkassen kunne ikke læses.',
|
||||
'error.mailboxSaid': 'Postkassen svarede: {what}',
|
||||
|
||||
@@ -61,6 +61,7 @@ export const en = {
|
||||
'nav.upcoming': 'Upcoming',
|
||||
'nav.delegated': 'Waiting on others',
|
||||
'nav.inbox': 'Inbox',
|
||||
'nav.calendar': 'Calendar',
|
||||
'nav.settings': 'Settings',
|
||||
'nav.signOut': 'Sign out',
|
||||
'nav.main': 'Main menu',
|
||||
@@ -582,6 +583,29 @@ export const en = {
|
||||
'int.server': 'Server',
|
||||
'int.caldavAuth':
|
||||
'The username is your email. The password is an API token — not your real password.',
|
||||
'int.calendar': 'Google Calendar',
|
||||
'int.calendarHint':
|
||||
'Read, never written. The events appear in Calendar beside your own tasks with due dates — they cannot be moved or completed from here, because they belong to Google.',
|
||||
'int.connectCalendar': 'Connect Google Calendar',
|
||||
'int.calendarConnectedAs': 'Connected as',
|
||||
'int.whichCalendars': 'Which calendars are shown',
|
||||
'int.noCalendars': 'The account has no calendars to show.',
|
||||
'int.calendarLastSync': 'Last fetched {when}.',
|
||||
'int.calendarNeverSynced': 'Not fetched yet.',
|
||||
'int.calendarFetchedOne': '{n} event fetched.',
|
||||
'int.calendarFetchedMany': '{n} events fetched.',
|
||||
'int.calendarNothingNew': 'No events in the window.',
|
||||
'int.calendarConnected': 'Google Calendar is connected.',
|
||||
'int.calendarState': 'Google’s reply did not match the attempt that started here. Try again.',
|
||||
'int.calendarExpired': 'The attempt expired on the way. Try again.',
|
||||
'int.calendarInvalid': 'Google’s reply could not be read. Try again.',
|
||||
'int.calendarFailed': 'Google refused to exchange the code.',
|
||||
'int.calendarNoRefresh':
|
||||
'Google sent no refresh token. Remove verdande from your Google account and connect again.',
|
||||
'int.calendarDenied': 'Access was refused at Google.',
|
||||
'int.calendarOrgInternal':
|
||||
'The Google app is registered as Internal and only accepts accounts inside the organisation. A private account cannot consent to it, whatever the screen says.',
|
||||
'int.calendarOther': 'Google replied: {what}',
|
||||
'int.gmailFetchedOne': '{n} task fetched.',
|
||||
'int.gmailFetchedMany': '{n} tasks fetched.',
|
||||
'int.gmailNothingNew': 'No new messages.',
|
||||
@@ -595,6 +619,16 @@ export const en = {
|
||||
'int.gmailDenied': 'Access was refused at Google.',
|
||||
'int.gmailOther': 'Gmail replied: {what}',
|
||||
|
||||
// --- the calendar, with Google’s events laid over ------------------------------------
|
||||
'cal.title': 'Calendar',
|
||||
'cal.notConnected': 'No calendar is connected yet. That is done under Settings → Integrations.',
|
||||
'cal.settings': 'Settings',
|
||||
'cal.allDay': 'All day',
|
||||
'cal.openInGoogle': 'Open in Google Calendar',
|
||||
'cal.beyondWindow':
|
||||
'Events are kept from {from} to {to}. Outside that the grid shows only your own tasks.',
|
||||
'cal.eventCannotMove': '{name} — an event in {calendar}. It cannot be moved from here.',
|
||||
|
||||
// --- a project ----------------------------------------------------------------------
|
||||
'project.name': 'The project’s name',
|
||||
'project.clickToRename': 'Click to rename',
|
||||
@@ -736,6 +770,10 @@ export const en = {
|
||||
'This instance cannot restart itself: {missing} are not set. A container cannot replace its own image — the panel does the work, and this is only the way to ask.',
|
||||
|
||||
'error.gmailSaid': 'Gmail said: {what}',
|
||||
'error.calendarRefused': 'Google refused the call. Try connecting the calendar again.',
|
||||
'error.calendarSaid': 'Google replied: {what}',
|
||||
'int.disconnectCalendarQuestion':
|
||||
'Disconnect Google Calendar? The tokens are forgotten and the fetched copy of the events is deleted. Your own tasks are untouched.',
|
||||
|
||||
'error.mailboxRefused': 'The mailbox could not be read.',
|
||||
'error.mailboxSaid': 'The mailbox answered: {what}',
|
||||
|
||||
Reference in New Issue
Block a user