From 94d89c9b437cf440a84d33f826d544343f638eb3 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:40:21 -0700 Subject: [PATCH 1/8] Patch for snappymail --- patches/029-patch_snappymail_add_to_calendar.patch | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 patches/029-patch_snappymail_add_to_calendar.patch diff --git a/patches/029-patch_snappymail_add_to_calendar.patch b/patches/029-patch_snappymail_add_to_calendar.patch new file mode 100644 index 00000000..a9081410 --- /dev/null +++ b/patches/029-patch_snappymail_add_to_calendar.patch @@ -0,0 +1,11 @@ +--- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/langs/en.ini 2023-09-20 08:34:31 ++++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/langs/en-new.ini 2023-09-20 08:38:33 +@@ -1,7 +1,7 @@ + [NEXTCLOUD] + SAVE_ATTACHMENTS = "Save in Nextcloud" + SAVE_EML = "Save as .eml in Nextcloud" +-SAVE_ICS = "Put in Calendar" ++SAVE_ICS = "Add to calendar" + SELECT_FOLDER = "Select folder" + SELECT_FILES = "Select file(s)" + ATTACH_FILES = "Attach Nextcloud files" -- GitLab From 78fec7b2b48586440c9a63f9bdd87e7aba313bb3 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:44:21 -0700 Subject: [PATCH 2/8] webdav changes --- patches/030_patch_snappymail_webdavapi.patch | 870 +++++++++++++++++++ 1 file changed, 870 insertions(+) create mode 100644 patches/030_patch_snappymail_webdavapi.patch diff --git a/patches/030_patch_snappymail_webdavapi.patch b/patches/030_patch_snappymail_webdavapi.patch new file mode 100644 index 00000000..37277543 --- /dev/null +++ b/patches/030_patch_snappymail_webdavapi.patch @@ -0,0 +1,870 @@ +--- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav.js 2023-09-20 08:34:31 ++++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav-new.js 2023-09-20 08:42:37 +@@ -1,428 +1,458 @@ + (rl => { + +-const +- nsDAV = 'DAV:', +- nsNC = 'http://nextcloud.org/ns', +- nsOC = 'http://owncloud.org/ns', +- nsOCS = 'http://open-collaboration-services.org/ns', +- nsCalDAV = 'urn:ietf:params:xml:ns:caldav', +- +- OC = () => parent.OC, +- +- // Nextcloud 19 deprecated generateUrl, but screw `import { generateUrl } from "@nextcloud/router"` +- shareUrl = () => OC().webroot + '/ocs/v2.php/apps/files_sharing/api/v1/shares', +- generateUrl = path => OC().webroot + (OC().config.modRewriteWorking ? '' : '/index.php') + path, +- generateRemoteUrl = path => location.protocol + '//' + location.host + generateUrl(path), +- +-// shareTypes = {0 = user, 1 = group, 3 = public link} +- +- propfindFiles = ` +- +- +- +- +- +- +- +- +- +-`, +- +- propfindCal = ` +- +- +- +- +- +- +-`, +- +- xmlParser = new DOMParser(), +- pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g, +- +- getElementsByTagName = (parent, namespace, localName) => parent.getElementsByTagNameNS(namespace, localName), +- getDavElementsByTagName = (parent, localName) => getElementsByTagName(parent, nsDAV, localName), +- getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), +- getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), +- +- ncFetch = (path, options) => { +- if (!OC().requestToken) { +- return Promise.reject(new Error('OC.requestToken missing')); +- } +- options = Object.assign({ +- mode: 'same-origin', +- cache: 'no-cache', +- redirect: 'error', +- credentials: 'same-origin', +- headers: {} +- }, options); +- options.headers.requesttoken = OC().requestToken; +- return fetch(path, options); +- }, +- +- davFetch = (mode, path, options) => { +- let cfg = rl.settings.get('Nextcloud'); +-// cfg.UID = document.head.dataset.user +- return ncFetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options); +- }, +- +- davFetchFiles = (path, options) => davFetch('files', path, options), +- +- createDirectory = path => davFetchFiles(path, { method: 'MKCOL' }), +- +- fetchFiles = path => { +- if (!OC().requestToken) { +- return Promise.reject(new Error('OC.requestToken missing')); +- } +- return davFetchFiles(path, { +- method: 'PROPFIND', +- headers: { +- 'Content-Type': 'application/xml; charset=utf-8' +- }, +- body: propfindFiles +- }) +- .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) +- .then(text => { +- const +- elemList = [], +- responseList = getDavElementsByTagName( +- xmlParser.parseFromString(text, 'application/xml').documentElement, +- 'response' +- ); +- path = path.replace(/\/$/, ''); +- for (let i = 0; i < responseList.length; ++i) { ++ const ++ nsDAV = 'DAV:', ++ nsNC = 'http://nextcloud.org/ns', ++ nsOC = 'http://owncloud.org/ns', ++ nsOCS = 'http://open-collaboration-services.org/ns', ++ nsCalDAV = 'urn:ietf:params:xml:ns:caldav', ++ ++ OC = () => parent.OC, ++ ++ // Nextcloud 19 deprecated generateUrl, but screw `import { generateUrl } from "@nextcloud/router"` ++ shareUrl = () => OC().webroot + '/ocs/v2.php/apps/files_sharing/api/v1/shares', ++ generateUrl = path => OC().webroot + (OC().config.modRewriteWorking ? '' : '/index.php') + path, ++ generateRemoteUrl = path => location.protocol + '//' + location.host + generateUrl(path), ++ ++ // shareTypes = {0 = user, 1 = group, 3 = public link} ++ ++ propfindFiles = ` ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ `, ++ ++ propfindCal = ` ++ ++ ++ ++ ++ ++ ++ ++ `, ++ ++ xmlParser = new DOMParser(), ++ pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g, ++ ++ getElementsByTagName = (parent, namespace, localName) => parent.getElementsByTagNameNS(namespace, localName), ++ getDavElementsByTagName = (parent, localName) => getElementsByTagName(parent, nsDAV, localName), ++ getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), ++ getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), ++ ++ ncFetch = (path, options) => { ++ if (!OC().requestToken) { ++ return Promise.reject(new Error('OC.requestToken missing')); ++ } ++ options = Object.assign({ ++ mode: 'same-origin', ++ cache: 'no-cache', ++ redirect: 'error', ++ credentials: 'same-origin', ++ headers: {} ++ }, options); ++ options.headers.requesttoken = OC().requestToken; ++ return fetch(path, options); ++ }, ++ ++ davFetch = (mode, path, options) => { ++ let cfg = rl.settings.get('Nextcloud'); ++ // cfg.UID = document.head.dataset.user ++ return ncFetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options); ++ }, ++ ++ davFetchFiles = (path, options) => davFetch('files', path, options), ++ ++ createDirectory = path => davFetchFiles(path, { method: 'MKCOL' }), ++ ++ fetchFiles = path => { ++ if (!OC().requestToken) { ++ return Promise.reject(new Error('OC.requestToken missing')); ++ } ++ return davFetchFiles(path, { ++ method: 'PROPFIND', ++ headers: { ++ 'Content-Type': 'application/xml; charset=utf-8' ++ }, ++ body: propfindFiles ++ }) ++ .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) ++ .then(text => { + const +- e = responseList.item(i), +- elem = { +- name: decodeURIComponent(getDavElementByTagName(e, 'href').textContent +- .replace(pathRegex, '').replace(/\/$/, '')), +- isFile: false +- }; +- if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { +- // skip current directory +- if (elem.name === path) { +- continue; ++ elemList = [], ++ responseList = getDavElementsByTagName( ++ xmlParser.parseFromString(text, 'application/xml').documentElement, ++ 'response' ++ ); ++ path = path.replace(/\/$/, ''); ++ for (let i = 0; i < responseList.length; ++i) { ++ const ++ e = responseList.item(i), ++ elem = { ++ name: decodeURIComponent(getDavElementByTagName(e, 'href').textContent ++ .replace(pathRegex, '').replace(/\/$/, '')), ++ isFile: false ++ }; ++ if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { ++ // skip current directory ++ if (elem.name === path) { ++ continue; ++ } ++ } else { ++ elem.isFile = true; ++ elem.id = e.getElementsByTagNameNS(nsOC, 'fileid')?.item(0)?.textContent; ++ elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent ++ || getElementByTagName(e, 'oc:size')?.textContent; ++ elem.shared = [...e.getElementsByTagNameNS(nsOC, 'share-type')].some(node => '3' == node.textContent); + } +- } else { +- elem.isFile = true; +- elem.id = e.getElementsByTagNameNS(nsOC, 'fileid')?.item(0)?.textContent; +- elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent +- || getElementByTagName(e, 'oc:size')?.textContent; +- elem.shared = [...e.getElementsByTagNameNS(nsOC, 'share-type')].some(node => '3' == node.textContent); ++ elemList.push(elem); + } +- elemList.push(elem); +- } +- return Promise.resolve(elemList); +- }); +- }, +- +- buildTree = (view, parent, items, path) => { +- if (items.length) { +- items.forEach(item => { +- if (!item.isFile) { +- let li = document.createElement('li'), +- details = document.createElement('details'), +- summary = document.createElement('summary'), +- ul = document.createElement('ul'); +- details.addEventListener('toggle', () => { +- ul.children.length +- || fetchFiles(item.name).then(items => buildTree(view, ul, items, item.name)); +- }); +- summary.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); +- summary.dataset.icon = '📁'; +- if (!view.files()) { +- let btn = document.createElement('button'); +- btn.name = 'select'; +- btn.textContent = 'select'; +- btn.className = 'button-vue'; +- summary.append(btn); +- summary.item_name = item.name; +- } +- details.append(summary); +- details.append(ul); +-// a.append('- ' + item.name.replace(/^\/+/, '')); +- li.append(details); +- parent.append(li); +- } ++ return Promise.resolve(elemList); + }); +- if (view.files()) { ++ }, ++ ++ buildTree = (view, parent, items, path) => { ++ if (items.length) { + items.forEach(item => { +- if (item.isFile) { ++ if (!item.isFile) { + let li = document.createElement('li'), +- cb = document.createElement('input'); +- +- li.item = item; +- li.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); +- li.dataset.icon = '🗎'; +- +- cb.type = 'checkbox'; +- li.append(cb); +- ++ details = document.createElement('details'), ++ summary = document.createElement('summary'), ++ ul = document.createElement('ul'); ++ details.addEventListener('toggle', () => { ++ ul.children.length ++ || fetchFiles(item.name).then(items => buildTree(view, ul, items, item.name)); ++ }); ++ summary.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); ++ summary.dataset.icon = '📁'; ++ if (!view.files()) { ++ let btn = document.createElement('button'); ++ btn.name = 'select'; ++ btn.textContent = 'select'; ++ btn.className = 'button-vue'; ++ summary.append(btn); ++ summary.item_name = item.name; ++ } ++ details.append(summary); ++ details.append(ul); ++ // a.append('- ' + item.name.replace(/^\/+/, '')); ++ li.append(details); + parent.append(li); + } + }); ++ if (view.files()) { ++ items.forEach(item => { ++ if (item.isFile) { ++ let li = document.createElement('li'), ++ cb = document.createElement('input'); ++ ++ li.item = item; ++ li.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); ++ li.dataset.icon = '🗎'; ++ ++ cb.type = 'checkbox'; ++ li.append(cb); ++ ++ parent.append(li); ++ } ++ }); ++ } + } ++ if (!view.files()) { ++ let li = document.createElement('li'), ++ input = document.createElement('input'), ++ btn = document.createElement('button'); ++ btn.name = 'create'; ++ btn.textContent = 'create & select'; ++ btn.className = 'button-vue'; ++ btn.input = input; ++ li.item_path = path; ++ li.append(input); ++ li.append(btn); ++ parent.append(li); ++ } ++ }; ++ ++ class NextcloudFilesPopupView extends rl.pluginPopupView { ++ constructor() { ++ super('NextcloudFiles'); ++ this.addObservables({ ++ files: false ++ }); + } +- if (!view.files()) { +- let li = document.createElement('li'), +- input = document.createElement('input'), +- btn = document.createElement('button'); +- btn.name = 'create'; +- btn.textContent = 'create & select'; +- btn.className = 'button-vue'; +- btn.input = input; +- li.item_path = path; +- li.append(input); +- li.append(btn); +- parent.append(li); ++ ++ attach() { ++ this.select = []; ++ this.tree.querySelectorAll('input').forEach(input => ++ input.checked && this.select.push(input.parentNode.item) ++ ); ++ this.close(); + } +- }; +- +-class NextcloudFilesPopupView extends rl.pluginPopupView { +- constructor() { +- super('NextcloudFiles'); +- this.addObservables({ +- files: false +- }); +- } +- +- attach() { +- this.select = []; +- this.tree.querySelectorAll('input').forEach(input => +- input.checked && this.select.push(input.parentNode.item) +- ); +- this.close(); +- } +- +- shareInternal() { +- this.select = []; +- this.tree.querySelectorAll('input').forEach(input => +- input.checked && this.select.push({url:generateRemoteUrl(`/f/${input.parentNode.item.id}`)}) +- ); +- this.close(); +- } +- +- sharePublic() { +- const inputs = [...this.tree.querySelectorAll('input')], +- loop = () => { +- if (!inputs.length) { +- this.close(); +- return; +- } +- const input = inputs.pop(); +- if (!input.checked) { +- loop(); +- } else { +- const item = input.parentNode.item; +- if (item.shared) { +- ncFetch( +- shareUrl() + `?format=json&path=${encodeURIComponent(item.name)}&reshares=true` +- ) +- .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) +- .then(json => { +- this.select.push({url:json.ocs.data[0].url}); +- loop(); +-// json.data[0].password +- }); +- } else { +- ncFetch( +- shareUrl(), +- { +- method:'POST', +- headers: { +- Accept: 'application/json', +- 'Content-Type': 'application/json' +- }, +- body: JSON.stringify({ +- path:item.name, +- shareType:3, +- attributes:"[]" +- }) +- } +- ) +- .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) +- .then(json => { +-// PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2 {"password":"ABC09"} +- this.select.push({url:json.ocs.data.url}); +- loop(); +- }); +- } +- } +- }; +- +- this.select = []; +- loop(); ++ ++ shareInternal() { ++ this.select = []; ++ this.tree.querySelectorAll('input').forEach(input => ++ input.checked && this.select.push({url:generateRemoteUrl(`/f/${input.parentNode.item.id}`)}) ++ ); ++ this.close(); ++ } ++ ++ sharePublic() { ++ const inputs = [...this.tree.querySelectorAll('input')], ++ loop = () => { ++ if (!inputs.length) { ++ this.close(); ++ return; ++ } ++ const input = inputs.pop(); ++ if (!input.checked) { ++ loop(); ++ } else { ++ const item = input.parentNode.item; ++ if (item.shared) { ++ ncFetch( ++ shareUrl() + `?format=json&path=${encodeURIComponent(item.name)}&reshares=true` ++ ) ++ .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) ++ .then(json => { ++ this.select.push({url:json.ocs.data[0].url}); ++ loop(); ++ // json.data[0].password ++ }); ++ } else { ++ ncFetch( ++ shareUrl(), ++ { ++ method:'POST', ++ headers: { ++ Accept: 'application/json', ++ 'Content-Type': 'application/json' ++ }, ++ body: JSON.stringify({ ++ path:item.name, ++ shareType:3, ++ attributes:"[]" ++ }) ++ } ++ ) ++ .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) ++ .then(json => { ++ // PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2 {"password":"ABC09"} ++ this.select.push({url:json.ocs.data.url}); ++ loop(); ++ }); ++ } ++ } ++ }; ++ ++ this.select = []; ++ loop(); ++ } ++ ++ onBuild(dom) { ++ this.tree = dom.querySelector('#sm-nc-files-tree'); ++ this.tree.addEventListener('click', event => { ++ let el = event.target; ++ if (el.matches('button')) { ++ let parent = el.parentNode; ++ if ('select' == el.name) { ++ this.select = parent.item_name; ++ this.close(); ++ } else if ('create' == el.name) { ++ let name = el.input.value.replace(/[|\\?*<":>+[]\/&\s]/g, ''); ++ if (name.length) { ++ name = parent.item_path + '/' + name; ++ createDirectory(name).then(response => { ++ if (response.status == 201) { ++ this.select = name; ++ this.close(); ++ } ++ }); ++ } ++ } ++ } ++ }); ++ } ++ ++ // Happens after showModal() ++ beforeShow(files, fResolve) { ++ this.select = ''; ++ this.files(!!files); ++ this.fResolve = fResolve; ++ ++ this.tree.innerHTML = ''; ++ fetchFiles('/').then(items => { ++ buildTree(this, this.tree, items, '/'); ++ }).catch(err => console.error(err)) ++ } ++ ++ onHide() { ++ this.fResolve(this.select); ++ } ++ /* ++ beforeShow() {} // Happens before showModal() ++ onShow() {} // Happens after showModal() ++ afterShow() {} // Happens after showModal() animation transitionend ++ onHide() {} // Happens before animation transitionend ++ afterHide() {} // Happens after animation transitionend ++ close() {} ++ */ + } +- +- onBuild(dom) { +- this.tree = dom.querySelector('#sm-nc-files-tree'); +- this.tree.addEventListener('click', event => { +- let el = event.target; +- if (el.matches('button')) { +- let parent = el.parentNode; +- if ('select' == el.name) { +- this.select = parent.item_name; ++ ++ class NextcloudCalendarsPopupView extends rl.pluginPopupView { ++ constructor() { ++ super('NextcloudCalendars'); ++ } ++ ++ onBuild(dom) { ++ this.tree = dom.querySelector('#sm-nc-calendars'); ++ this.tree.addEventListener('click', event => { ++ let el = event.target; ++ if (el.matches('button')) { ++ this.select = el.href; + this.close(); +- } else if ('create' == el.name) { +- let name = el.input.value.replace(/[|\\?*<":>+[]\/&\s]/g, ''); +- if (name.length) { +- name = parent.item_path + '/' + name; +- createDirectory(name).then(response => { +- if (response.status == 201) { +- this.select = name; +- this.close(); +- } +- }); ++ } ++ }); ++ } ++ ++ // Happens after showModal() ++ beforeShow(fResolve) { ++ this.select = ''; ++ this.fResolve = fResolve; ++ this.tree.innerHTML = ''; ++ davFetch('calendars', '/', { ++ method: 'PROPFIND', ++ headers: { ++ 'Content-Type': 'application/xml; charset=utf-8' ++ }, ++ body: propfindCal ++ }) ++ .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) ++ .then(text => { ++ const ++ responseList = getDavElementsByTagName( ++ xmlParser.parseFromString(text, 'application/xml').documentElement, ++ 'response' ++ ); ++ for (let i = 0; i < responseList.length; ++i) { ++ const e = responseList.item(i); ++ if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { ++ // && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { ++ const li = document.createElement('li'); ++ li.style.display = 'flex'; ++ // Create the element ++ const span = document.createElement('span'); ++ span.setAttribute('role', 'img'); ++ span.className = 'material-design-icon checkbox-blank-circle-icon'; ++ span.style.fill = getDavElementByTagName(e, 'calendar-color').textContent; ++ span.style.width = '20px'; // Width ++ span.style.height = '20px'; // Height ++ ++ // Create the element ++ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); ++ svg.setAttribute('width', '20'); ++ svg.setAttribute('height', '20'); ++ svg.setAttribute('viewBox', '0 0 24 24'); ++ ++ // Create the element within the element ++ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); ++ path.setAttribute('d', 'M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z'); ++ svg.appendChild(path); ++ ++ // Append the element to the element ++ span.appendChild(svg); ++ ++ const button = document.createElement('button'); ++ button.className = 'button-vue'; ++ button.style.backgroundColor = 'transparent'; ++ button.style.border = '0'; ++ button.style.fontSize = 'large'; ++ button.style.padding = '0'; ++ button.style.cursor = 'pointer'; ++ button.textContent = getDavElementByTagName(e, 'displayname').textContent; ++ button.style.color = 'black'; ++ ++ li.appendChild(span); ++ li.appendChild(button); ++ ++ // Assuming 'this.tree' is a reference to the parent element where you want to append the 'li' element. ++ this.tree.appendChild(li); + } + } +- } +- }); ++ }) ++ .catch(err => console.error(err)); ++ } ++ ++ onHide() { ++ this.fResolve(this.select); ++ } ++ /* ++ beforeShow() {} // Happens before showModal() ++ onShow() {} // Happens after showModal() ++ afterShow() {} // Happens after showModal() animation transitionend ++ onHide() {} // Happens before animation transitionend ++ afterHide() {} // Happens after animation transitionend ++ close() {} ++ */ + } +- +- // Happens after showModal() +- beforeShow(files, fResolve) { +- this.select = ''; +- this.files(!!files); +- this.fResolve = fResolve; +- +- this.tree.innerHTML = ''; +- fetchFiles('/').then(items => { +- buildTree(this, this.tree, items, '/'); +- }).catch(err => console.error(err)) +- } +- +- onHide() { +- this.fResolve(this.select); +- } +-/* +-beforeShow() {} // Happens before showModal() +-onShow() {} // Happens after showModal() +-afterShow() {} // Happens after showModal() animation transitionend +-onHide() {} // Happens before animation transitionend +-afterHide() {} // Happens after animation transitionend +-close() {} +-*/ +-} +- +-class NextcloudCalendarsPopupView extends rl.pluginPopupView { +- constructor() { +- super('NextcloudCalendars'); +- } +- +- onBuild(dom) { +- this.tree = dom.querySelector('#sm-nc-calendars'); +- this.tree.addEventListener('click', event => { +- let el = event.target; +- if (el.matches('button')) { +- this.select = el.href; +- this.close(); +- } +- }); +- } +- +- // Happens after showModal() +- beforeShow(fResolve) { +- this.select = ''; +- this.fResolve = fResolve; +- this.tree.innerHTML = ''; +- davFetch('calendars', '/', { +- method: 'PROPFIND', +- headers: { +- 'Content-Type': 'application/xml; charset=utf-8' +- }, +- body: propfindCal +- }) +- .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) +- .then(text => { +- const +- responseList = getDavElementsByTagName( +- xmlParser.parseFromString(text, 'application/xml').documentElement, +- 'response' +- ); +- for (let i = 0; i < responseList.length; ++i) { +- const e = responseList.item(i); +- if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { +-// && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { +- const li = document.createElement('li'), +- btn = document.createElement('button'); +- li.dataset.icon = '📅'; +- li.textContent = getDavElementByTagName(e, 'displayname').textContent; +- btn.href = getDavElementByTagName(e, 'href').textContent +- .replace(pathRegex, '').replace(/\/$/, ''); +- btn.textContent = 'select'; +- btn.className = 'button-vue'; +- btn.style.marginLeft = '1em'; +- li.append(btn); +- this.tree.append(li); ++ ++ rl.nextcloud = { ++ selectCalendar: () => ++ new Promise(resolve => { ++ NextcloudCalendarsPopupView.showModal([ ++ href => resolve(href), ++ ]); ++ }), ++ ++ calendarPut: (path, event) => { ++ davFetch('calendars', path + '/' + event.UID + '.ics', { ++ method: 'PUT', ++ headers: { ++ 'Content-Type': 'text/calendar' ++ }, ++ // Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property. ++ body: event.rawText ++ .replace('METHOD:', 'X-METHOD:') ++ // https://github.com/nextcloud/calendar/issues/4684 ++ .replace('ATTENDEE:', 'X-ATTENDEE:') ++ .replace('ORGANIZER:', 'X-ORGANIZER:') ++ .replace(/RSVP=TRUE/g, 'RSVP=FALSE') ++ .replace(/\r?\n/g, '\r\n') ++ }) ++ .then(response => { ++ if (201 == response.status) { ++ // Created ++ } else if (204 == response.status) { ++ // Not modified ++ } else { ++ // response.text().then(text => console.error({status:response.status, body:text})); ++ Promise.reject(new Error({ response })); + } +- } +- }) +- .catch(err => console.error(err)); +- } +- +- onHide() { +- this.fResolve(this.select); +- } +-/* +-beforeShow() {} // Happens before showModal() +-onShow() {} // Happens after showModal() +-afterShow() {} // Happens after showModal() animation transitionend +-onHide() {} // Happens before animation transitionend +-afterHide() {} // Happens after animation transitionend +-close() {} +-*/ +-} +- +-rl.nextcloud = { +- selectCalendar: () => +- new Promise(resolve => { +- NextcloudCalendarsPopupView.showModal([ +- href => resolve(href), +- ]); +- }), +- +- calendarPut: (path, event) => { +- davFetch('calendars', path + '/' + event.UID + '.ics', { +- method: 'PUT', +- headers: { +- 'Content-Type': 'text/calendar' +- }, +- // Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property. +- body: event.rawText +- .replace('METHOD:', 'X-METHOD:') +- // https://github.com/nextcloud/calendar/issues/4684 +- .replace('ATTENDEE:', 'X-ATTENDEE:') +- .replace('ORGANIZER:', 'X-ORGANIZER:') +- .replace(/RSVP=TRUE/g, 'RSVP=FALSE') +- .replace(/\r?\n/g, '\r\n') +- }) +- .then(response => { +- if (201 == response.status) { +- // Created +- } else if (204 == response.status) { +- // Not modified +- } else { +-// response.text().then(text => console.error({status:response.status, body:text})); +- Promise.reject(new Error({ response })); +- } +- }); +- }, +- +- selectFolder: () => +- new Promise(resolve => { +- NextcloudFilesPopupView.showModal([ +- false, +- folder => resolve(folder), +- ]); +- }), +- +- selectFiles: () => +- new Promise(resolve => { +- NextcloudFilesPopupView.showModal([ +- true, +- files => resolve(files), +- ]); +- }) +-}; +- +-})(window.rl); ++ }); ++ }, ++ ++ selectFolder: () => ++ new Promise(resolve => { ++ NextcloudFilesPopupView.showModal([ ++ false, ++ folder => resolve(folder), ++ ]); ++ }), ++ ++ selectFiles: () => ++ new Promise(resolve => { ++ NextcloudFilesPopupView.showModal([ ++ true, ++ files => resolve(files), ++ ]); ++ }) ++ }; ++ ++ })(window.rl); ++ +\ No newline at end of file -- GitLab From 9442f7ecb240a5e371c1b2a98ad05247ce28d823 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:48:17 -0700 Subject: [PATCH 3/8] webdav --- patches/030-patch_snappymail_webdavapi.patch | 67 ++ patches/030_patch_snappymail_webdavapi.patch | 870 ------------------- 2 files changed, 67 insertions(+), 870 deletions(-) create mode 100644 patches/030-patch_snappymail_webdavapi.patch delete mode 100644 patches/030_patch_snappymail_webdavapi.patch diff --git a/patches/030-patch_snappymail_webdavapi.patch b/patches/030-patch_snappymail_webdavapi.patch new file mode 100644 index 00000000..502e753f --- /dev/null +++ b/patches/030-patch_snappymail_webdavapi.patch @@ -0,0 +1,67 @@ +--- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav.js 2023-09-20 08:34:31 ++++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav-new.js 2023-09-20 08:47:37 +@@ -34,6 +34,7 @@ + + + ++ + + `, + +@@ -343,17 +344,45 @@ + const e = responseList.item(i); + if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { + // && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { +- const li = document.createElement('li'), +- btn = document.createElement('button'); +- li.dataset.icon = '📅'; +- li.textContent = getDavElementByTagName(e, 'displayname').textContent; +- btn.href = getDavElementByTagName(e, 'href').textContent +- .replace(pathRegex, '').replace(/\/$/, ''); +- btn.textContent = 'select'; +- btn.className = 'button-vue'; +- btn.style.marginLeft = '1em'; +- li.append(btn); +- this.tree.append(li); ++ const li = document.createElement('li'); ++ li.style.display = 'flex'; ++ // Create the element ++ const span = document.createElement('span'); ++ span.setAttribute('role', 'img'); ++ span.className = 'material-design-icon checkbox-blank-circle-icon'; ++ span.style.fill = getDavElementByTagName(e, 'calendar-color').textContent; ++ span.style.width = '20px'; // Width ++ span.style.height = '20px'; // Height ++ ++ // Create the element ++ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); ++ svg.setAttribute('width', '20'); ++ svg.setAttribute('height', '20'); ++ svg.setAttribute('viewBox', '0 0 24 24'); ++ ++ // Create the element within the element ++ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); ++ path.setAttribute('d', 'M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z'); ++ svg.appendChild(path); ++ ++ // Append the element to the element ++ span.appendChild(svg); ++ ++ const button = document.createElement('button'); ++ button.className = 'button-vue'; ++ button.style.backgroundColor = 'transparent'; ++ button.style.border = '0'; ++ button.style.fontSize = 'large'; ++ button.style.padding = '0'; ++ button.style.cursor = 'pointer'; ++ button.textContent = getDavElementByTagName(e, 'displayname').textContent; ++ button.style.color = 'black'; ++ ++ li.appendChild(span); ++ li.appendChild(button); ++ ++ // Assuming 'this.tree' is a reference to the parent element where you want to append the 'li' element. ++ this.tree.appendChild(li); + } + } + }) diff --git a/patches/030_patch_snappymail_webdavapi.patch b/patches/030_patch_snappymail_webdavapi.patch deleted file mode 100644 index 37277543..00000000 --- a/patches/030_patch_snappymail_webdavapi.patch +++ /dev/null @@ -1,870 +0,0 @@ ---- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav.js 2023-09-20 08:34:31 -+++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav-new.js 2023-09-20 08:42:37 -@@ -1,428 +1,458 @@ - (rl => { - --const -- nsDAV = 'DAV:', -- nsNC = 'http://nextcloud.org/ns', -- nsOC = 'http://owncloud.org/ns', -- nsOCS = 'http://open-collaboration-services.org/ns', -- nsCalDAV = 'urn:ietf:params:xml:ns:caldav', -- -- OC = () => parent.OC, -- -- // Nextcloud 19 deprecated generateUrl, but screw `import { generateUrl } from "@nextcloud/router"` -- shareUrl = () => OC().webroot + '/ocs/v2.php/apps/files_sharing/api/v1/shares', -- generateUrl = path => OC().webroot + (OC().config.modRewriteWorking ? '' : '/index.php') + path, -- generateRemoteUrl = path => location.protocol + '//' + location.host + generateUrl(path), -- --// shareTypes = {0 = user, 1 = group, 3 = public link} -- -- propfindFiles = ` -- -- -- -- -- -- -- -- -- --`, -- -- propfindCal = ` -- -- -- -- -- -- --`, -- -- xmlParser = new DOMParser(), -- pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g, -- -- getElementsByTagName = (parent, namespace, localName) => parent.getElementsByTagNameNS(namespace, localName), -- getDavElementsByTagName = (parent, localName) => getElementsByTagName(parent, nsDAV, localName), -- getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), -- getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), -- -- ncFetch = (path, options) => { -- if (!OC().requestToken) { -- return Promise.reject(new Error('OC.requestToken missing')); -- } -- options = Object.assign({ -- mode: 'same-origin', -- cache: 'no-cache', -- redirect: 'error', -- credentials: 'same-origin', -- headers: {} -- }, options); -- options.headers.requesttoken = OC().requestToken; -- return fetch(path, options); -- }, -- -- davFetch = (mode, path, options) => { -- let cfg = rl.settings.get('Nextcloud'); --// cfg.UID = document.head.dataset.user -- return ncFetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options); -- }, -- -- davFetchFiles = (path, options) => davFetch('files', path, options), -- -- createDirectory = path => davFetchFiles(path, { method: 'MKCOL' }), -- -- fetchFiles = path => { -- if (!OC().requestToken) { -- return Promise.reject(new Error('OC.requestToken missing')); -- } -- return davFetchFiles(path, { -- method: 'PROPFIND', -- headers: { -- 'Content-Type': 'application/xml; charset=utf-8' -- }, -- body: propfindFiles -- }) -- .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) -- .then(text => { -- const -- elemList = [], -- responseList = getDavElementsByTagName( -- xmlParser.parseFromString(text, 'application/xml').documentElement, -- 'response' -- ); -- path = path.replace(/\/$/, ''); -- for (let i = 0; i < responseList.length; ++i) { -+ const -+ nsDAV = 'DAV:', -+ nsNC = 'http://nextcloud.org/ns', -+ nsOC = 'http://owncloud.org/ns', -+ nsOCS = 'http://open-collaboration-services.org/ns', -+ nsCalDAV = 'urn:ietf:params:xml:ns:caldav', -+ -+ OC = () => parent.OC, -+ -+ // Nextcloud 19 deprecated generateUrl, but screw `import { generateUrl } from "@nextcloud/router"` -+ shareUrl = () => OC().webroot + '/ocs/v2.php/apps/files_sharing/api/v1/shares', -+ generateUrl = path => OC().webroot + (OC().config.modRewriteWorking ? '' : '/index.php') + path, -+ generateRemoteUrl = path => location.protocol + '//' + location.host + generateUrl(path), -+ -+ // shareTypes = {0 = user, 1 = group, 3 = public link} -+ -+ propfindFiles = ` -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ `, -+ -+ propfindCal = ` -+ -+ -+ -+ -+ -+ -+ -+ `, -+ -+ xmlParser = new DOMParser(), -+ pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g, -+ -+ getElementsByTagName = (parent, namespace, localName) => parent.getElementsByTagNameNS(namespace, localName), -+ getDavElementsByTagName = (parent, localName) => getElementsByTagName(parent, nsDAV, localName), -+ getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), -+ getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), -+ -+ ncFetch = (path, options) => { -+ if (!OC().requestToken) { -+ return Promise.reject(new Error('OC.requestToken missing')); -+ } -+ options = Object.assign({ -+ mode: 'same-origin', -+ cache: 'no-cache', -+ redirect: 'error', -+ credentials: 'same-origin', -+ headers: {} -+ }, options); -+ options.headers.requesttoken = OC().requestToken; -+ return fetch(path, options); -+ }, -+ -+ davFetch = (mode, path, options) => { -+ let cfg = rl.settings.get('Nextcloud'); -+ // cfg.UID = document.head.dataset.user -+ return ncFetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options); -+ }, -+ -+ davFetchFiles = (path, options) => davFetch('files', path, options), -+ -+ createDirectory = path => davFetchFiles(path, { method: 'MKCOL' }), -+ -+ fetchFiles = path => { -+ if (!OC().requestToken) { -+ return Promise.reject(new Error('OC.requestToken missing')); -+ } -+ return davFetchFiles(path, { -+ method: 'PROPFIND', -+ headers: { -+ 'Content-Type': 'application/xml; charset=utf-8' -+ }, -+ body: propfindFiles -+ }) -+ .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) -+ .then(text => { - const -- e = responseList.item(i), -- elem = { -- name: decodeURIComponent(getDavElementByTagName(e, 'href').textContent -- .replace(pathRegex, '').replace(/\/$/, '')), -- isFile: false -- }; -- if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { -- // skip current directory -- if (elem.name === path) { -- continue; -+ elemList = [], -+ responseList = getDavElementsByTagName( -+ xmlParser.parseFromString(text, 'application/xml').documentElement, -+ 'response' -+ ); -+ path = path.replace(/\/$/, ''); -+ for (let i = 0; i < responseList.length; ++i) { -+ const -+ e = responseList.item(i), -+ elem = { -+ name: decodeURIComponent(getDavElementByTagName(e, 'href').textContent -+ .replace(pathRegex, '').replace(/\/$/, '')), -+ isFile: false -+ }; -+ if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { -+ // skip current directory -+ if (elem.name === path) { -+ continue; -+ } -+ } else { -+ elem.isFile = true; -+ elem.id = e.getElementsByTagNameNS(nsOC, 'fileid')?.item(0)?.textContent; -+ elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent -+ || getElementByTagName(e, 'oc:size')?.textContent; -+ elem.shared = [...e.getElementsByTagNameNS(nsOC, 'share-type')].some(node => '3' == node.textContent); - } -- } else { -- elem.isFile = true; -- elem.id = e.getElementsByTagNameNS(nsOC, 'fileid')?.item(0)?.textContent; -- elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent -- || getElementByTagName(e, 'oc:size')?.textContent; -- elem.shared = [...e.getElementsByTagNameNS(nsOC, 'share-type')].some(node => '3' == node.textContent); -+ elemList.push(elem); - } -- elemList.push(elem); -- } -- return Promise.resolve(elemList); -- }); -- }, -- -- buildTree = (view, parent, items, path) => { -- if (items.length) { -- items.forEach(item => { -- if (!item.isFile) { -- let li = document.createElement('li'), -- details = document.createElement('details'), -- summary = document.createElement('summary'), -- ul = document.createElement('ul'); -- details.addEventListener('toggle', () => { -- ul.children.length -- || fetchFiles(item.name).then(items => buildTree(view, ul, items, item.name)); -- }); -- summary.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); -- summary.dataset.icon = '📁'; -- if (!view.files()) { -- let btn = document.createElement('button'); -- btn.name = 'select'; -- btn.textContent = 'select'; -- btn.className = 'button-vue'; -- summary.append(btn); -- summary.item_name = item.name; -- } -- details.append(summary); -- details.append(ul); --// a.append('- ' + item.name.replace(/^\/+/, '')); -- li.append(details); -- parent.append(li); -- } -+ return Promise.resolve(elemList); - }); -- if (view.files()) { -+ }, -+ -+ buildTree = (view, parent, items, path) => { -+ if (items.length) { - items.forEach(item => { -- if (item.isFile) { -+ if (!item.isFile) { - let li = document.createElement('li'), -- cb = document.createElement('input'); -- -- li.item = item; -- li.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); -- li.dataset.icon = '🗎'; -- -- cb.type = 'checkbox'; -- li.append(cb); -- -+ details = document.createElement('details'), -+ summary = document.createElement('summary'), -+ ul = document.createElement('ul'); -+ details.addEventListener('toggle', () => { -+ ul.children.length -+ || fetchFiles(item.name).then(items => buildTree(view, ul, items, item.name)); -+ }); -+ summary.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); -+ summary.dataset.icon = '📁'; -+ if (!view.files()) { -+ let btn = document.createElement('button'); -+ btn.name = 'select'; -+ btn.textContent = 'select'; -+ btn.className = 'button-vue'; -+ summary.append(btn); -+ summary.item_name = item.name; -+ } -+ details.append(summary); -+ details.append(ul); -+ // a.append('- ' + item.name.replace(/^\/+/, '')); -+ li.append(details); - parent.append(li); - } - }); -+ if (view.files()) { -+ items.forEach(item => { -+ if (item.isFile) { -+ let li = document.createElement('li'), -+ cb = document.createElement('input'); -+ -+ li.item = item; -+ li.textContent = item.name.replace(/^.*\/([^/]+)$/, '$1'); -+ li.dataset.icon = '🗎'; -+ -+ cb.type = 'checkbox'; -+ li.append(cb); -+ -+ parent.append(li); -+ } -+ }); -+ } - } -+ if (!view.files()) { -+ let li = document.createElement('li'), -+ input = document.createElement('input'), -+ btn = document.createElement('button'); -+ btn.name = 'create'; -+ btn.textContent = 'create & select'; -+ btn.className = 'button-vue'; -+ btn.input = input; -+ li.item_path = path; -+ li.append(input); -+ li.append(btn); -+ parent.append(li); -+ } -+ }; -+ -+ class NextcloudFilesPopupView extends rl.pluginPopupView { -+ constructor() { -+ super('NextcloudFiles'); -+ this.addObservables({ -+ files: false -+ }); - } -- if (!view.files()) { -- let li = document.createElement('li'), -- input = document.createElement('input'), -- btn = document.createElement('button'); -- btn.name = 'create'; -- btn.textContent = 'create & select'; -- btn.className = 'button-vue'; -- btn.input = input; -- li.item_path = path; -- li.append(input); -- li.append(btn); -- parent.append(li); -+ -+ attach() { -+ this.select = []; -+ this.tree.querySelectorAll('input').forEach(input => -+ input.checked && this.select.push(input.parentNode.item) -+ ); -+ this.close(); - } -- }; -- --class NextcloudFilesPopupView extends rl.pluginPopupView { -- constructor() { -- super('NextcloudFiles'); -- this.addObservables({ -- files: false -- }); -- } -- -- attach() { -- this.select = []; -- this.tree.querySelectorAll('input').forEach(input => -- input.checked && this.select.push(input.parentNode.item) -- ); -- this.close(); -- } -- -- shareInternal() { -- this.select = []; -- this.tree.querySelectorAll('input').forEach(input => -- input.checked && this.select.push({url:generateRemoteUrl(`/f/${input.parentNode.item.id}`)}) -- ); -- this.close(); -- } -- -- sharePublic() { -- const inputs = [...this.tree.querySelectorAll('input')], -- loop = () => { -- if (!inputs.length) { -- this.close(); -- return; -- } -- const input = inputs.pop(); -- if (!input.checked) { -- loop(); -- } else { -- const item = input.parentNode.item; -- if (item.shared) { -- ncFetch( -- shareUrl() + `?format=json&path=${encodeURIComponent(item.name)}&reshares=true` -- ) -- .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) -- .then(json => { -- this.select.push({url:json.ocs.data[0].url}); -- loop(); --// json.data[0].password -- }); -- } else { -- ncFetch( -- shareUrl(), -- { -- method:'POST', -- headers: { -- Accept: 'application/json', -- 'Content-Type': 'application/json' -- }, -- body: JSON.stringify({ -- path:item.name, -- shareType:3, -- attributes:"[]" -- }) -- } -- ) -- .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) -- .then(json => { --// PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2 {"password":"ABC09"} -- this.select.push({url:json.ocs.data.url}); -- loop(); -- }); -- } -- } -- }; -- -- this.select = []; -- loop(); -+ -+ shareInternal() { -+ this.select = []; -+ this.tree.querySelectorAll('input').forEach(input => -+ input.checked && this.select.push({url:generateRemoteUrl(`/f/${input.parentNode.item.id}`)}) -+ ); -+ this.close(); -+ } -+ -+ sharePublic() { -+ const inputs = [...this.tree.querySelectorAll('input')], -+ loop = () => { -+ if (!inputs.length) { -+ this.close(); -+ return; -+ } -+ const input = inputs.pop(); -+ if (!input.checked) { -+ loop(); -+ } else { -+ const item = input.parentNode.item; -+ if (item.shared) { -+ ncFetch( -+ shareUrl() + `?format=json&path=${encodeURIComponent(item.name)}&reshares=true` -+ ) -+ .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) -+ .then(json => { -+ this.select.push({url:json.ocs.data[0].url}); -+ loop(); -+ // json.data[0].password -+ }); -+ } else { -+ ncFetch( -+ shareUrl(), -+ { -+ method:'POST', -+ headers: { -+ Accept: 'application/json', -+ 'Content-Type': 'application/json' -+ }, -+ body: JSON.stringify({ -+ path:item.name, -+ shareType:3, -+ attributes:"[]" -+ }) -+ } -+ ) -+ .then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response }))) -+ .then(json => { -+ // PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2 {"password":"ABC09"} -+ this.select.push({url:json.ocs.data.url}); -+ loop(); -+ }); -+ } -+ } -+ }; -+ -+ this.select = []; -+ loop(); -+ } -+ -+ onBuild(dom) { -+ this.tree = dom.querySelector('#sm-nc-files-tree'); -+ this.tree.addEventListener('click', event => { -+ let el = event.target; -+ if (el.matches('button')) { -+ let parent = el.parentNode; -+ if ('select' == el.name) { -+ this.select = parent.item_name; -+ this.close(); -+ } else if ('create' == el.name) { -+ let name = el.input.value.replace(/[|\\?*<":>+[]\/&\s]/g, ''); -+ if (name.length) { -+ name = parent.item_path + '/' + name; -+ createDirectory(name).then(response => { -+ if (response.status == 201) { -+ this.select = name; -+ this.close(); -+ } -+ }); -+ } -+ } -+ } -+ }); -+ } -+ -+ // Happens after showModal() -+ beforeShow(files, fResolve) { -+ this.select = ''; -+ this.files(!!files); -+ this.fResolve = fResolve; -+ -+ this.tree.innerHTML = ''; -+ fetchFiles('/').then(items => { -+ buildTree(this, this.tree, items, '/'); -+ }).catch(err => console.error(err)) -+ } -+ -+ onHide() { -+ this.fResolve(this.select); -+ } -+ /* -+ beforeShow() {} // Happens before showModal() -+ onShow() {} // Happens after showModal() -+ afterShow() {} // Happens after showModal() animation transitionend -+ onHide() {} // Happens before animation transitionend -+ afterHide() {} // Happens after animation transitionend -+ close() {} -+ */ - } -- -- onBuild(dom) { -- this.tree = dom.querySelector('#sm-nc-files-tree'); -- this.tree.addEventListener('click', event => { -- let el = event.target; -- if (el.matches('button')) { -- let parent = el.parentNode; -- if ('select' == el.name) { -- this.select = parent.item_name; -+ -+ class NextcloudCalendarsPopupView extends rl.pluginPopupView { -+ constructor() { -+ super('NextcloudCalendars'); -+ } -+ -+ onBuild(dom) { -+ this.tree = dom.querySelector('#sm-nc-calendars'); -+ this.tree.addEventListener('click', event => { -+ let el = event.target; -+ if (el.matches('button')) { -+ this.select = el.href; - this.close(); -- } else if ('create' == el.name) { -- let name = el.input.value.replace(/[|\\?*<":>+[]\/&\s]/g, ''); -- if (name.length) { -- name = parent.item_path + '/' + name; -- createDirectory(name).then(response => { -- if (response.status == 201) { -- this.select = name; -- this.close(); -- } -- }); -+ } -+ }); -+ } -+ -+ // Happens after showModal() -+ beforeShow(fResolve) { -+ this.select = ''; -+ this.fResolve = fResolve; -+ this.tree.innerHTML = ''; -+ davFetch('calendars', '/', { -+ method: 'PROPFIND', -+ headers: { -+ 'Content-Type': 'application/xml; charset=utf-8' -+ }, -+ body: propfindCal -+ }) -+ .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) -+ .then(text => { -+ const -+ responseList = getDavElementsByTagName( -+ xmlParser.parseFromString(text, 'application/xml').documentElement, -+ 'response' -+ ); -+ for (let i = 0; i < responseList.length; ++i) { -+ const e = responseList.item(i); -+ if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { -+ // && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { -+ const li = document.createElement('li'); -+ li.style.display = 'flex'; -+ // Create the element -+ const span = document.createElement('span'); -+ span.setAttribute('role', 'img'); -+ span.className = 'material-design-icon checkbox-blank-circle-icon'; -+ span.style.fill = getDavElementByTagName(e, 'calendar-color').textContent; -+ span.style.width = '20px'; // Width -+ span.style.height = '20px'; // Height -+ -+ // Create the element -+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); -+ svg.setAttribute('width', '20'); -+ svg.setAttribute('height', '20'); -+ svg.setAttribute('viewBox', '0 0 24 24'); -+ -+ // Create the element within the element -+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); -+ path.setAttribute('d', 'M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z'); -+ svg.appendChild(path); -+ -+ // Append the element to the element -+ span.appendChild(svg); -+ -+ const button = document.createElement('button'); -+ button.className = 'button-vue'; -+ button.style.backgroundColor = 'transparent'; -+ button.style.border = '0'; -+ button.style.fontSize = 'large'; -+ button.style.padding = '0'; -+ button.style.cursor = 'pointer'; -+ button.textContent = getDavElementByTagName(e, 'displayname').textContent; -+ button.style.color = 'black'; -+ -+ li.appendChild(span); -+ li.appendChild(button); -+ -+ // Assuming 'this.tree' is a reference to the parent element where you want to append the 'li' element. -+ this.tree.appendChild(li); - } - } -- } -- }); -+ }) -+ .catch(err => console.error(err)); -+ } -+ -+ onHide() { -+ this.fResolve(this.select); -+ } -+ /* -+ beforeShow() {} // Happens before showModal() -+ onShow() {} // Happens after showModal() -+ afterShow() {} // Happens after showModal() animation transitionend -+ onHide() {} // Happens before animation transitionend -+ afterHide() {} // Happens after animation transitionend -+ close() {} -+ */ - } -- -- // Happens after showModal() -- beforeShow(files, fResolve) { -- this.select = ''; -- this.files(!!files); -- this.fResolve = fResolve; -- -- this.tree.innerHTML = ''; -- fetchFiles('/').then(items => { -- buildTree(this, this.tree, items, '/'); -- }).catch(err => console.error(err)) -- } -- -- onHide() { -- this.fResolve(this.select); -- } --/* --beforeShow() {} // Happens before showModal() --onShow() {} // Happens after showModal() --afterShow() {} // Happens after showModal() animation transitionend --onHide() {} // Happens before animation transitionend --afterHide() {} // Happens after animation transitionend --close() {} --*/ --} -- --class NextcloudCalendarsPopupView extends rl.pluginPopupView { -- constructor() { -- super('NextcloudCalendars'); -- } -- -- onBuild(dom) { -- this.tree = dom.querySelector('#sm-nc-calendars'); -- this.tree.addEventListener('click', event => { -- let el = event.target; -- if (el.matches('button')) { -- this.select = el.href; -- this.close(); -- } -- }); -- } -- -- // Happens after showModal() -- beforeShow(fResolve) { -- this.select = ''; -- this.fResolve = fResolve; -- this.tree.innerHTML = ''; -- davFetch('calendars', '/', { -- method: 'PROPFIND', -- headers: { -- 'Content-Type': 'application/xml; charset=utf-8' -- }, -- body: propfindCal -- }) -- .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) -- .then(text => { -- const -- responseList = getDavElementsByTagName( -- xmlParser.parseFromString(text, 'application/xml').documentElement, -- 'response' -- ); -- for (let i = 0; i < responseList.length; ++i) { -- const e = responseList.item(i); -- if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { --// && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { -- const li = document.createElement('li'), -- btn = document.createElement('button'); -- li.dataset.icon = '📅'; -- li.textContent = getDavElementByTagName(e, 'displayname').textContent; -- btn.href = getDavElementByTagName(e, 'href').textContent -- .replace(pathRegex, '').replace(/\/$/, ''); -- btn.textContent = 'select'; -- btn.className = 'button-vue'; -- btn.style.marginLeft = '1em'; -- li.append(btn); -- this.tree.append(li); -+ -+ rl.nextcloud = { -+ selectCalendar: () => -+ new Promise(resolve => { -+ NextcloudCalendarsPopupView.showModal([ -+ href => resolve(href), -+ ]); -+ }), -+ -+ calendarPut: (path, event) => { -+ davFetch('calendars', path + '/' + event.UID + '.ics', { -+ method: 'PUT', -+ headers: { -+ 'Content-Type': 'text/calendar' -+ }, -+ // Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property. -+ body: event.rawText -+ .replace('METHOD:', 'X-METHOD:') -+ // https://github.com/nextcloud/calendar/issues/4684 -+ .replace('ATTENDEE:', 'X-ATTENDEE:') -+ .replace('ORGANIZER:', 'X-ORGANIZER:') -+ .replace(/RSVP=TRUE/g, 'RSVP=FALSE') -+ .replace(/\r?\n/g, '\r\n') -+ }) -+ .then(response => { -+ if (201 == response.status) { -+ // Created -+ } else if (204 == response.status) { -+ // Not modified -+ } else { -+ // response.text().then(text => console.error({status:response.status, body:text})); -+ Promise.reject(new Error({ response })); - } -- } -- }) -- .catch(err => console.error(err)); -- } -- -- onHide() { -- this.fResolve(this.select); -- } --/* --beforeShow() {} // Happens before showModal() --onShow() {} // Happens after showModal() --afterShow() {} // Happens after showModal() animation transitionend --onHide() {} // Happens before animation transitionend --afterHide() {} // Happens after animation transitionend --close() {} --*/ --} -- --rl.nextcloud = { -- selectCalendar: () => -- new Promise(resolve => { -- NextcloudCalendarsPopupView.showModal([ -- href => resolve(href), -- ]); -- }), -- -- calendarPut: (path, event) => { -- davFetch('calendars', path + '/' + event.UID + '.ics', { -- method: 'PUT', -- headers: { -- 'Content-Type': 'text/calendar' -- }, -- // Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property. -- body: event.rawText -- .replace('METHOD:', 'X-METHOD:') -- // https://github.com/nextcloud/calendar/issues/4684 -- .replace('ATTENDEE:', 'X-ATTENDEE:') -- .replace('ORGANIZER:', 'X-ORGANIZER:') -- .replace(/RSVP=TRUE/g, 'RSVP=FALSE') -- .replace(/\r?\n/g, '\r\n') -- }) -- .then(response => { -- if (201 == response.status) { -- // Created -- } else if (204 == response.status) { -- // Not modified -- } else { --// response.text().then(text => console.error({status:response.status, body:text})); -- Promise.reject(new Error({ response })); -- } -- }); -- }, -- -- selectFolder: () => -- new Promise(resolve => { -- NextcloudFilesPopupView.showModal([ -- false, -- folder => resolve(folder), -- ]); -- }), -- -- selectFiles: () => -- new Promise(resolve => { -- NextcloudFilesPopupView.showModal([ -- true, -- files => resolve(files), -- ]); -- }) --}; -- --})(window.rl); -+ }); -+ }, -+ -+ selectFolder: () => -+ new Promise(resolve => { -+ NextcloudFilesPopupView.showModal([ -+ false, -+ folder => resolve(folder), -+ ]); -+ }), -+ -+ selectFiles: () => -+ new Promise(resolve => { -+ NextcloudFilesPopupView.showModal([ -+ true, -+ files => resolve(files), -+ ]); -+ }) -+ }; -+ -+ })(window.rl); -+ -\ No newline at end of file -- GitLab From 1503d4b64af0f960b9e4aa4f6a6c1d903d5fc852 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:50:36 -0700 Subject: [PATCH 4/8] open added --- patches/031-patch_snappymail_open_attachment.patch | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 patches/031-patch_snappymail_open_attachment.patch diff --git a/patches/031-patch_snappymail_open_attachment.patch b/patches/031-patch_snappymail_open_attachment.patch new file mode 100644 index 00000000..114ea75c --- /dev/null +++ b/patches/031-patch_snappymail_open_attachment.patch @@ -0,0 +1,11 @@ +--- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html 2023-09-20 08:34:31 ++++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView-new.html 2023-09-20 08:50:10 +@@ -229,7 +229,7 @@ +
+
+-
++
+ + +
    -- GitLab From d67f3c569288eff5a05cbb9e62cfa7e93ce79db9 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:52:23 -0700 Subject: [PATCH 5/8] path changes --- patches/029-patch_snappymail_add_to_calendar.patch | 4 ++-- patches/030-patch_snappymail_webdavapi.patch | 4 ++-- patches/031-patch_snappymail_open_attachment.patch | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/patches/029-patch_snappymail_add_to_calendar.patch b/patches/029-patch_snappymail_add_to_calendar.patch index a9081410..77d6b361 100644 --- a/patches/029-patch_snappymail_add_to_calendar.patch +++ b/patches/029-patch_snappymail_add_to_calendar.patch @@ -1,5 +1,5 @@ ---- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/langs/en.ini 2023-09-20 08:34:31 -+++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/langs/en-new.ini 2023-09-20 08:38:33 +--- ./custom_apps/snappymail/plugins/nextcloud/langs/en.ini 2023-09-20 08:34:31 ++++ ./custom_apps/snappymail/plugins/nextcloud/langs/en-new.ini 2023-09-20 08:38:33 @@ -1,7 +1,7 @@ [NEXTCLOUD] SAVE_ATTACHMENTS = "Save in Nextcloud" diff --git a/patches/030-patch_snappymail_webdavapi.patch b/patches/030-patch_snappymail_webdavapi.patch index 502e753f..a0b2a9a4 100644 --- a/patches/030-patch_snappymail_webdavapi.patch +++ b/patches/030-patch_snappymail_webdavapi.patch @@ -1,5 +1,5 @@ ---- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav.js 2023-09-20 08:34:31 -+++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/plugins/nextcloud/js/webdav-new.js 2023-09-20 08:47:37 +--- ./custom_apps/snappymail/plugins/nextcloud/js/webdav.js 2023-09-20 08:34:31 ++++ ./custom_apps/snappymail/plugins/nextcloud/js/webdav-new.js 2023-09-20 08:47:37 @@ -34,6 +34,7 @@ diff --git a/patches/031-patch_snappymail_open_attachment.patch b/patches/031-patch_snappymail_open_attachment.patch index 114ea75c..c9603a05 100644 --- a/patches/031-patch_snappymail_open_attachment.patch +++ b/patches/031-patch_snappymail_open_attachment.patch @@ -1,5 +1,5 @@ ---- /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html 2023-09-20 08:34:31 -+++ /Users/ronak/Desktop/murena/nextcloud/ecloud_dev_example/volumes/nextcloud/html/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView-new.html 2023-09-20 08:50:10 +--- ./custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html 2023-09-20 08:34:31 ++++ ./custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView-new.html 2023-09-20 08:50:10 @@ -229,7 +229,7 @@
    Date: Wed, 20 Sep 2023 08:56:50 -0700 Subject: [PATCH 6/8] Dockerfile updated --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1a4b9a11..ee35819d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -143,6 +143,10 @@ RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/3rdparty/sabre/vobject/lib/ITip/Broke RUN patch -u ${BASE_DIR}/apps/dav/lib/CalDAV/Reminder/ReminderService.php -i ${TMP_PATCH_DIR}/024-reminder-service-handle-exception.patch RUN patch -u ${BASE_DIR}/apps/theming/lib/Themes/CommonThemeTrait.php -i ${TMP_PATCH_DIR}/026-primary-color-fix.patch +#snappymail patches +RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/plugins/nextcloud/langs/en.ini -i ${TMP_PATCH_DIR}/029-patch_snappymail_add_to_calendar.patch +RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html -i ${TMP_PATCH_DIR}/031-patch_snappymail_open_attachment.patch + RUN rm -rf ${TMP_PATCH_DIR} # Custom theme -- GitLab From 9b215b0306104debedd121e7f6b546da7e972241 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 08:57:56 -0700 Subject: [PATCH 7/8] Dockerfile updated --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ee35819d..ffdb6bca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -145,7 +145,7 @@ RUN patch -u ${BASE_DIR}/apps/theming/lib/Themes/CommonThemeTrait.php -i ${TMP_P #snappymail patches RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/plugins/nextcloud/langs/en.ini -i ${TMP_PATCH_DIR}/029-patch_snappymail_add_to_calendar.patch -RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html -i ${TMP_PATCH_DIR}/031-patch_snappymail_open_attachment.patch +RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/snappymail/v/${SNAPPY_VERSION}/app/templates/Views/User/MailMessageView.html -i ${TMP_PATCH_DIR}/031-patch_snappymail_open_attachment.patch RUN rm -rf ${TMP_PATCH_DIR} -- GitLab From abd52a096f70aa15d8bf68f852758ccf123e3b8f Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Wed, 20 Sep 2023 09:45:04 -0700 Subject: [PATCH 8/8] added 30 patch --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index ffdb6bca..8f6e2332 100644 --- a/Dockerfile +++ b/Dockerfile @@ -145,6 +145,8 @@ RUN patch -u ${BASE_DIR}/apps/theming/lib/Themes/CommonThemeTrait.php -i ${TMP_P #snappymail patches RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/plugins/nextcloud/langs/en.ini -i ${TMP_PATCH_DIR}/029-patch_snappymail_add_to_calendar.patch +#RUN cd ${BASE_DIR} && patch -u ${BASE_DIR_OF_DATA}/appdata_snappymail/_data_/_default_/plugins/nextcloud/js/webdav.js -i ${TMP_PATCH_DIR}/030-patch_snappymail_webdavapi.patch +#The path will be ./nextcloud/ecloud_dev_example/volumes/nextcloud/data/appdata_snappymail/_data_/_default_/plugins/nextcloud/js/webdav.js RUN cd ${BASE_DIR} && patch -u ${BASE_DIR}/custom_apps/snappymail/snappymail/v/${SNAPPY_VERSION}/app/templates/Views/User/MailMessageView.html -i ${TMP_PATCH_DIR}/031-patch_snappymail_open_attachment.patch RUN rm -rf ${TMP_PATCH_DIR} -- GitLab