From 679e96b8659bc18e5ec0d463531423060a118ffd Mon Sep 17 00:00:00 2001 From: "amirmohammd.ahmadi" Date: Sat, 27 Sep 2025 10:21:11 +0330 Subject: [PATCH 1/5] fix: RTL and Bidirectional Text Issues --- src/components/TextEditor.vue | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/components/TextEditor.vue b/src/components/TextEditor.vue index c84c4ddf23..542abe5c4d 100644 --- a/src/components/TextEditor.vue +++ b/src/components/TextEditor.vue @@ -202,6 +202,11 @@ export default { beforeMount() { this.loadEditorTranslations(getLanguage()) }, + mounted() { + this.$nextTick(() => { + this.setupRTLSupport() + }) + }, methods: { getLink(text) { const results = searchProvider(text) @@ -446,6 +451,70 @@ export default { } this.editorInstance.execute('insertItem', { content, isHtml: this.html }, '!') }, + setupRTLSupport() { + // Function to detect RTL characters + const isRTL = (text) => { + const rtlChars = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/ + return rtlChars.test(text) + } + + // Wait for editor to be ready + setTimeout(() => { + const editorElement = this.$el?.querySelector('.ck-editor__editable') + if (!editorElement) return + + // Add input event listener for auto RTL/LTR detection + editorElement.addEventListener('input', (e) => { + const selection = window.getSelection() + if (!selection.rangeCount) return + + const range = selection.getRangeAt(0) + const container = range.commonAncestorContainer + const textNode = container.nodeType === Node.TEXT_NODE ? container : container.firstChild + + if (textNode && textNode.textContent) { + const text = textNode.textContent + const parentElement = textNode.parentElement || textNode.parentNode + + if (isRTL(text)) { + parentElement.style.direction = 'rtl' + parentElement.style.textAlign = 'right' + parentElement.style.unicodeBidi = 'embed' + } else if (/^[a-zA-Z0-9\s.,!?;:'"()\-_+=<>{}[\]|\\/@#$%^&*`~]+$/.test(text)) { + parentElement.style.direction = 'ltr' + parentElement.style.textAlign = 'left' + parentElement.style.unicodeBidi = 'embed' + } + } + }) + + // Set initial direction based on content + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.TEXT_NODE && node.textContent) { + const text = node.textContent + const parentElement = node.parentElement || node.parentNode + + if (isRTL(text)) { + parentElement.style.direction = 'rtl' + parentElement.style.textAlign = 'right' + parentElement.style.unicodeBidi = 'embed' + } + } + }) + } + }) + }) + + observer.observe(editorElement, { + childList: true, + subtree: true, + characterData: true + }) + }, 1000) + }, }, } @@ -728,5 +797,34 @@ https://github.com/ckeditor/ckeditor5/issues/1142 background: var(--color-primary-element-light) !important; color: var(--color-main-text) !important; } +/* RTL Support for mixed content */ +.ck-editor__editable { + unicode-bidi: plaintext !important; + text-align: start !important; +} + +/* Better RTL handling for mixed Persian/English text */ +.ck-editor__editable p, +.ck-editor__editable div, +.ck-editor__editable span { + unicode-bidi: isolate !important; + text-align: start !important; +} + +/* Ensure proper text flow for mixed content */ +.ck-editor__editable * { + unicode-bidi: isolate !important; +} + +/* Force proper direction for text nodes */ +.ck-editor__editable [style*="direction: rtl"] { + direction: rtl !important; + text-align: right !important; +} + +.ck-editor__editable [style*="direction: ltr"] { + direction: ltr !important; + text-align: left !important; +} From 105a8d7fca5ced487e71fbb322276c37fc995178 Mon Sep 17 00:00:00 2001 From: "amirmohammd.ahmadi" Date: Sat, 27 Sep 2025 10:44:35 +0330 Subject: [PATCH 2/5] fix: issue with calendar invitations from external domains in Nextcloud Mail app/Calendar app --- src/components/Imip.vue | 95 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/components/Imip.vue b/src/components/Imip.vue index e9091c600e..dbdd86bd25 100644 --- a/src/components/Imip.vue +++ b/src/components/Imip.vue @@ -157,6 +157,22 @@ function findAttendee(vEvent, email) { return undefined } +function findAttendeeByEmails(vEvent, emails) { + if (!vEvent || !Array.isArray(emails) || emails.length === 0) { + return undefined + } + + const emailSet = new Set(emails.map(e => removeMailtoPrefix(e).toLowerCase())) + for (const attendee of [...vEvent.getPropertyIterator('ORGANIZER'), ...vEvent.getAttendeeIterator()]) { + const normalized = removeMailtoPrefix(attendee.email).toLowerCase() + if (emailSet.has(normalized)) { + return attendee + } + } + + return undefined +} + export default { name: 'Imip', components: { @@ -173,6 +189,10 @@ export default { type: Object, required: true, }, + message: { + type: Object, + required: true, + }, }, data() { return { @@ -197,6 +217,7 @@ export default { currentUserPrincipalEmail: 'getCurrentUserPrincipalEmail', clonedWriteableCalendars: 'getClonedWriteableCalendars', currentUserPrincipal: 'getCurrentUserPrincipal', + accounts: 'getAccounts', }), /** @@ -208,6 +229,14 @@ export default { return this.scheduling.method }, + isFromDigikala() { + if (!this.message.from || !this.message.from[0]) { + return false + } + const fromEmail = this.message.from[0].email?.toLowerCase() || '' + return fromEmail.endsWith('@digikala.com') + }, + /** * @return {boolean} */ @@ -305,7 +334,7 @@ export default { * @return {boolean} */ userIsAttendee() { - return !!findAttendee(this.attachedVEvent, this.currentUserPrincipalEmail) + return !!findAttendeeByEmails(this.attachedVEvent, this.allUserEmails) }, /** @@ -314,10 +343,38 @@ export default { * @return {string|undefined} */ existingParticipationStatus() { - const attendee = findAttendee(this.existingVEvent, this.currentUserPrincipalEmail) + const attendee = findAttendeeByEmails(this.existingVEvent, this.allUserEmails) return attendee?.participationStatus ?? undefined }, + /** + * All user's email addresses (principal + all mail account addresses and aliases) + * + * @return {string[]} + */ + allUserEmails() { + const emails = new Set() + if (this.currentUserPrincipalEmail) { + emails.add(this.currentUserPrincipalEmail.toLowerCase()) + } + if (Array.isArray(this.accounts)) { + for (const account of this.accounts) { + if (account?.emailAddress) { + emails.add(String(account.emailAddress).toLowerCase()) + } + if (Array.isArray(account?.aliases)) { + for (const alias of account.aliases) { + const address = alias?.alias || alias?.emailAddress + if (address) { + emails.add(String(address).toLowerCase()) + } + } + } + } + } + return Array.from(emails) + }, + /** * The status message to show in case of REPLY messages. * @@ -352,6 +409,14 @@ export default { }) }, + existingEventFetched: { + immediate: false, + async handler(fetched) { + if (!fetched) return + await this.autoCreateTentativeIfNeeded() + }, + }, + /** * List of calendar options for the target calendar picker. * @@ -410,6 +475,14 @@ export default { }, }, }, + + async mounted() { + // If data already fetched on mount, attempt auto-create once + if (this.existingEventFetched) { + await this.autoCreateTentativeIfNeeded() + } + }, + methods: { async accept() { await this.saveEventWithParticipationStatus(ACCEPTED) @@ -420,6 +493,22 @@ export default { async decline() { await this.saveEventWithParticipationStatus(DECLINED) }, + async autoCreateTentativeIfNeeded() { + try { + if ( + this.isRequest + && !this.wasProcessed + && this.userIsAttendee + && this.eventIsInFuture + && this.existingEventFetched + && !this.isExistingEvent + ) { + await this.saveEventWithParticipationStatus(TENTATIVE) + } + } catch (e) { + // ignore auto-create failures + } + }, async saveEventWithParticipationStatus(status) { let vCalendar if (this.isExistingEvent) { @@ -428,7 +517,7 @@ export default { vCalendar = this.attachedVCalendar } const vEvent = vCalendar.getFirstComponent('VEVENT') - const attendee = findAttendee(vEvent, this.currentUserPrincipalEmail) + const attendee = findAttendeeByEmails(vEvent, this.allUserEmails) if (!attendee) { return } From c493b7e37f49598c163dfcf86ad44ec2109e387d Mon Sep 17 00:00:00 2001 From: "amirmohammd.ahmadi" Date: Sat, 27 Sep 2025 10:49:09 +0330 Subject: [PATCH 3/5] fix bugs --- src/components/TextEditor.vue | 98 ----------------------------------- 1 file changed, 98 deletions(-) diff --git a/src/components/TextEditor.vue b/src/components/TextEditor.vue index 542abe5c4d..c84c4ddf23 100644 --- a/src/components/TextEditor.vue +++ b/src/components/TextEditor.vue @@ -202,11 +202,6 @@ export default { beforeMount() { this.loadEditorTranslations(getLanguage()) }, - mounted() { - this.$nextTick(() => { - this.setupRTLSupport() - }) - }, methods: { getLink(text) { const results = searchProvider(text) @@ -451,70 +446,6 @@ export default { } this.editorInstance.execute('insertItem', { content, isHtml: this.html }, '!') }, - setupRTLSupport() { - // Function to detect RTL characters - const isRTL = (text) => { - const rtlChars = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/ - return rtlChars.test(text) - } - - // Wait for editor to be ready - setTimeout(() => { - const editorElement = this.$el?.querySelector('.ck-editor__editable') - if (!editorElement) return - - // Add input event listener for auto RTL/LTR detection - editorElement.addEventListener('input', (e) => { - const selection = window.getSelection() - if (!selection.rangeCount) return - - const range = selection.getRangeAt(0) - const container = range.commonAncestorContainer - const textNode = container.nodeType === Node.TEXT_NODE ? container : container.firstChild - - if (textNode && textNode.textContent) { - const text = textNode.textContent - const parentElement = textNode.parentElement || textNode.parentNode - - if (isRTL(text)) { - parentElement.style.direction = 'rtl' - parentElement.style.textAlign = 'right' - parentElement.style.unicodeBidi = 'embed' - } else if (/^[a-zA-Z0-9\s.,!?;:'"()\-_+=<>{}[\]|\\/@#$%^&*`~]+$/.test(text)) { - parentElement.style.direction = 'ltr' - parentElement.style.textAlign = 'left' - parentElement.style.unicodeBidi = 'embed' - } - } - }) - - // Set initial direction based on content - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.TEXT_NODE && node.textContent) { - const text = node.textContent - const parentElement = node.parentElement || node.parentNode - - if (isRTL(text)) { - parentElement.style.direction = 'rtl' - parentElement.style.textAlign = 'right' - parentElement.style.unicodeBidi = 'embed' - } - } - }) - } - }) - }) - - observer.observe(editorElement, { - childList: true, - subtree: true, - characterData: true - }) - }, 1000) - }, }, } @@ -797,34 +728,5 @@ https://github.com/ckeditor/ckeditor5/issues/1142 background: var(--color-primary-element-light) !important; color: var(--color-main-text) !important; } -/* RTL Support for mixed content */ -.ck-editor__editable { - unicode-bidi: plaintext !important; - text-align: start !important; -} - -/* Better RTL handling for mixed Persian/English text */ -.ck-editor__editable p, -.ck-editor__editable div, -.ck-editor__editable span { - unicode-bidi: isolate !important; - text-align: start !important; -} - -/* Ensure proper text flow for mixed content */ -.ck-editor__editable * { - unicode-bidi: isolate !important; -} - -/* Force proper direction for text nodes */ -.ck-editor__editable [style*="direction: rtl"] { - direction: rtl !important; - text-align: right !important; -} - -.ck-editor__editable [style*="direction: ltr"] { - direction: ltr !important; - text-align: left !important; -} From e281c97a1cf2cfefd6c0783a55bcf4842accc470 Mon Sep 17 00:00:00 2001 From: "amirmohammd.ahmadi" Date: Mon, 20 Oct 2025 12:45:34 +0330 Subject: [PATCH 4/5] add console for catch --- src/components/Imip.vue | 2 +- y | 7 +++++++ y.pub | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 y create mode 100644 y.pub diff --git a/src/components/Imip.vue b/src/components/Imip.vue index dbdd86bd25..60121b6648 100644 --- a/src/components/Imip.vue +++ b/src/components/Imip.vue @@ -506,7 +506,7 @@ export default { await this.saveEventWithParticipationStatus(TENTATIVE) } } catch (e) { - // ignore auto-create failures + console.log("error", e) } }, async saveEventWithParticipationStatus(status) { diff --git a/y b/y new file mode 100644 index 0000000000..d2b8b0f3d0 --- /dev/null +++ b/y @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCtkOBeKymyWdv3qjRB7V66WCTnQw+X3evlBOiTo3VvWwAAAKD0Hy8b9B8v +GwAAAAtzc2gtZWQyNTUxOQAAACCtkOBeKymyWdv3qjRB7V66WCTnQw+X3evlBOiTo3VvWw +AAAECII2T26l6rmjxjcJfCMsA2GpbQ1LTqNbqnLxAErFCasq2Q4F4rKbJZ2/eqNEHtXrpY +JOdDD5fd6+UE6JOjdW9bAAAAFnlvdXJfZW1haWxAZXhhbXBsZS5jb20BAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/y.pub b/y.pub new file mode 100644 index 0000000000..c7cf35be0a --- /dev/null +++ b/y.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2Q4F4rKbJZ2/eqNEHtXrpYJOdDD5fd6+UE6JOjdW9b your_email@example.com From 8a7e992d8e71c2866ea322ada7e1a772097f93c2 Mon Sep 17 00:00:00 2001 From: "amirmohammd.ahmadi" Date: Wed, 22 Oct 2025 09:17:11 +0330 Subject: [PATCH 5/5] Remove leaked SSH key (private/public) --- y | 7 ------- y.pub | 1 - 2 files changed, 8 deletions(-) delete mode 100644 y delete mode 100644 y.pub diff --git a/y b/y deleted file mode 100644 index d2b8b0f3d0..0000000000 --- a/y +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACCtkOBeKymyWdv3qjRB7V66WCTnQw+X3evlBOiTo3VvWwAAAKD0Hy8b9B8v -GwAAAAtzc2gtZWQyNTUxOQAAACCtkOBeKymyWdv3qjRB7V66WCTnQw+X3evlBOiTo3VvWw -AAAECII2T26l6rmjxjcJfCMsA2GpbQ1LTqNbqnLxAErFCasq2Q4F4rKbJZ2/eqNEHtXrpY -JOdDD5fd6+UE6JOjdW9bAAAAFnlvdXJfZW1haWxAZXhhbXBsZS5jb20BAgMEBQYH ------END OPENSSH PRIVATE KEY----- diff --git a/y.pub b/y.pub deleted file mode 100644 index c7cf35be0a..0000000000 --- a/y.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2Q4F4rKbJZ2/eqNEHtXrpYJOdDD5fd6+UE6JOjdW9b your_email@example.com