diff --git a/lib/frontend/actions/events.js b/lib/frontend/actions/events.js new file mode 100644 index 0000000..554deb0 --- /dev/null +++ b/lib/frontend/actions/events.js @@ -0,0 +1,70 @@ +import { postURL } from '../services' +import { requestStore } from '../stores' +import { Request } from '../records' +import { uuidv4, parseStartTimeFromiCalString } from '../utils' +import { extractEventDataFromHTML } from '../../../lib/services/ics-retriever' +import generateICS from '../../../lib/services/ics-generator' + +const getEventHTML = async (url) => { + const formData = new URLSearchParams() + formData.set('url', url) + + try { + const request = new Request({ id: uuidv4() }) + requestStore.set(request) + const response = await postURL(formData) + const text = await response.text() + return text + } catch (error) { + requestStore.update((prevRequest) => { + prevRequest.error = error + return prevRequest + }) + return null + } +} + +const createICS = (html, url, { logger }) => { + try { + // TODO: set parsing status in UI + + const eventData = extractEventDataFromHTML(html, url, { logger }) + generateICS(eventData) + .then((text) => { + const dataUri = encodeURIComponent(text) + const uri = `data:text/calendar;charset=utf-8,${dataUri}` + console.log(`SUCCESS - uri: ${uri}`) + + // TODO: create download link + // link.setAttribute('href', uri) + // link.setAttribute('download', 'download.ics') + // link.click() + + // input.value = '' + + const summaryMatch = text.match(/SUMMARY:.*/)[0] + const summary = summaryMatch ? summaryMatch.replace(/SUMMARY:/, '') : '' + const startTimeMatches = text.match(/DTSTART:.*/) + const startTimeMatch = text.length > 0 ? + (startTimeMatches[0] || '').replace(/DTSTART:/, '') : + '' + const startTime = parseStartTimeFromiCalString(startTimeMatch) + + // TODO: save record to a store + // createRecord(uri, summary, startTime) + + // TODO: clear UI status + // clearStatuses() + }) + // TODO: catch errors + .catch(alert) + } catch (err) { + // TODO: catch errors + alert(err) + } +} + +export const createEvent = async (url, { logger }) => { + const html = await getEventHTML(url) + const ics = await createICS(html, url, { logger }) +} diff --git a/lib/frontend/actions/index.js b/lib/frontend/actions/index.js new file mode 100644 index 0000000..66b41ed --- /dev/null +++ b/lib/frontend/actions/index.js @@ -0,0 +1,5 @@ +import { createEvent } from './events' + +export { + createEvent, +} diff --git a/lib/frontend/components/AppContainer.svelte b/lib/frontend/components/AppContainer.svelte index cfb6551..7cee4e5 100644 --- a/lib/frontend/components/AppContainer.svelte +++ b/lib/frontend/components/AppContainer.svelte @@ -1,4 +1,5 @@ + +
diff --git a/lib/frontend/services/index.js b/lib/frontend/services/index.js index d05033b..46dfa35 100644 --- a/lib/frontend/services/index.js +++ b/lib/frontend/services/index.js @@ -1,5 +1,7 @@ +import { postURL } from './network' import storageListener from './storageListener' export { + postURL, storageListener, } diff --git a/lib/frontend/services/network.js b/lib/frontend/services/network.js new file mode 100644 index 0000000..8a4b814 --- /dev/null +++ b/lib/frontend/services/network.js @@ -0,0 +1,23 @@ +export const postURL = (data) => { + return new Promise((resolve, reject) => { + fetch('/download/html/', { + method: 'POST', + headers: { + 'Accept': 'text/html, application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: data, + }).then((response) => { + if (response.status !== 200) { + if (response.body.constructor === ReadableStream) { + response.json().then((json) => reject(json.error || response.statusText)) + return + } + reject(response.statusText) + return + } + + resolve(response) + }).catch(reject) + }) +} diff --git a/lib/frontend/utils.js b/lib/frontend/utils.js index 1412440..c9de08a 100644 --- a/lib/frontend/utils.js +++ b/lib/frontend/utils.js @@ -23,3 +23,26 @@ export const sortRecord = (a, b) => { return 0 } +// NOTE: Generate random IDs: https://stackoverflow.com/a/2117523/3056783 +export const uuidv4 = () => { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ) +} + +export const parseStartTimeFromiCalString = (text = '') => { + const [ dateStr, timeStr ] = text.split('T') + const rawDate = dateStr || '' + const rawTime = timeStr || '' + + const year = Number(rawDate.slice(0, 4)) + const month = Number(Math.max(rawDate.slice(4, 6) - 1), 0) + const date = Number(rawDate.slice(6, 8)) + const hour = Number(rawTime.slice(0, 2)) + const minutes = Number(rawTime.slice(2, 4)) + const seconds = Number(rawTime.slice(4, 6)) + + const parsedDate = new Date(year, month, date, hour, minutes, seconds) + return parsedDate.toString() +} + diff --git a/lib/static/index.html b/lib/static/index.html index 397121d..1a7cd76 100644 --- a/lib/static/index.html +++ b/lib/static/index.html @@ -23,22 +23,22 @@ file that you can import into your calendar. Only public events are supported. - -