Skip to content

Commit c3b7de4

Browse files
Merge pull request #3164 from frappe/main-hotfix
chore(release): hotfix to main
2 parents 0d82fa0 + ba49e22 commit c3b7de4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+17366
-6597
lines changed

desk/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
"dependencies": {
1212
"@headlessui/vue": "^1.7.22",
1313
"@tailwindcss/typography": "^0.5.9",
14-
"@tiptap/core": "^2.2.4",
14+
"@tiptap/core": "^3.11.0",
1515
"@twilio/voice-sdk": "^2.15.0",
1616
"@vee-validate/zod": "^4.8.2",
1717
"@vitejs/plugin-vue": "^4.2.3",
1818
"@vueuse/core": "^13.8.0",
1919
"@vueuse/integrations": "^13.8.0",
2020
"dayjs": "^1.11.7",
21-
"frappe-ui": "0.1.261",
21+
"frappe-ui": "0.1.269",
2222
"gemoji": "^8.1.0",
2323
"mime": "^3.0.0",
2424
"pinia": "^2.0.33",
@@ -41,6 +41,6 @@
4141
},
4242
"resolutions": {
4343
"cheerio": "1.0.0-rc.12",
44-
"prosemirror-model": "1.25.1"
44+
"prosemirror-model": "1.25.4"
4545
}
4646
}

desk/public/ticketActivity.png

210 KB
Loading

desk/public/timers.png

67.4 KB
Loading
2.69 MB
Binary file not shown.

desk/public/videos/mailVideo.mp4

2.16 MB
Binary file not shown.
3.81 MB
Binary file not shown.

desk/src/components/EmailEditor.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,16 +440,16 @@ onMounted(() => {
440440
441441
function handleSelectAll(e: KeyboardEvent) {
442442
const active = document.activeElement;
443-
const editorDom = editorRef.value?.editor?.view?.dom as
444-
| HTMLElement
445-
| undefined;
443+
const editorContext = editorRef.value?.editor;
444+
const editorDom = editorContext?.view?.dom as HTMLElement | undefined;
446445
const quotedEl = quotedContentRef.value;
447446
const sel = window.getSelection();
448447
if (!sel || !editorDom) return;
449448
if (!editorDom.contains(active) && !(quotedEl && quotedEl.contains(active))) {
450449
return;
451450
}
452451
e.preventDefault();
452+
editorContext?.commands.selectAll();
453453
sel.removeAllRanges();
454454
const range = document.createRange();
455455

desk/src/components/Section.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
</div>
4646
</template>
4747
<script setup>
48-
import { ref } from "vue";
48+
import { ref, watch } from "vue";
4949
5050
const props = defineProps({
5151
label: {
@@ -81,6 +81,13 @@ const props = defineProps({
8181
const hide = ref(props.hideLabel);
8282
const opened = ref(props.opened);
8383
84+
watch(
85+
() => props.opened,
86+
(val) => {
87+
opened.value = val;
88+
}
89+
);
90+
8491
function toggle() {
8592
opened.value = !opened.value;
8693
}

desk/src/components/Settings/EmailAdd.vue

Lines changed: 242 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,29 @@
5252
:key="field.name"
5353
class="flex flex-col gap-1"
5454
>
55+
<div
56+
v-if="field.name === 'domain'"
57+
class="flex flex-col gap-2"
58+
>
59+
<Link
60+
v-model="customState.domain"
61+
:label="field.label"
62+
:placeholder="field.placeholder"
63+
doctype="Email Domain"
64+
:onCreate="handleCreateDomainClick"
65+
/>
66+
<div v-if="customState.domain" class="flex gap-2">
67+
<Button
68+
size="sm"
69+
variant="subtle"
70+
theme="gray"
71+
:label="__('Edit Domain')"
72+
@click="handleEditDomainClick"
73+
/>
74+
</div>
75+
</div>
5576
<FormControl
77+
v-else
5678
v-model="state[field.name]"
5779
:label="field.label"
5880
:name="field.name"
@@ -61,6 +83,41 @@
6183
/>
6284
</div>
6385
</div>
86+
<div
87+
v-if="isCustomSelected"
88+
class="grid grid-cols-1 md:grid-cols-2 gap-4"
89+
>
90+
<div class="flex flex-col gap-4">
91+
<div
92+
v-for="field in customIncomingFields"
93+
:key="field.name"
94+
class="flex flex-col gap-1"
95+
>
96+
<FormControl
97+
v-model="customState[field.name]"
98+
:label="field.label"
99+
:name="field.name"
100+
:type="field.type"
101+
:placeholder="field.placeholder"
102+
/>
103+
</div>
104+
</div>
105+
<div class="flex flex-col gap-4">
106+
<div
107+
v-for="field in customOutgoingFields"
108+
:key="field.name"
109+
class="flex flex-col gap-1"
110+
>
111+
<FormControl
112+
v-model="customState[field.name]"
113+
:label="field.label"
114+
:name="field.name"
115+
:type="field.type"
116+
:placeholder="field.placeholder"
117+
/>
118+
</div>
119+
</div>
120+
</div>
64121
<div class="grid grid-cols-2 gap-4">
65122
<div
66123
v-for="field in incomingOutgoingFields"
@@ -109,10 +166,13 @@ import { capture } from "@/telemetry";
109166
import { EmailAccount, EmailService, EmailStep } from "@/types";
110167
import { call, createResource, toast } from "frappe-ui";
111168
import { useOnboarding } from "frappe-ui/frappe";
112-
import { computed, Reactive, reactive, Ref, ref } from "vue";
169+
import { computed, reactive, Ref, ref, watch } from "vue";
113170
import CircleAlert from "~icons/lucide/circle-alert";
114171
import {
115-
customProviderFields,
172+
customIncomingFields,
173+
customOutgoingFields,
174+
customProviderTopFields,
175+
frappeMailFields,
116176
incomingOutgoingFields,
117177
popularProviderFields,
118178
services,
@@ -125,11 +185,45 @@ interface E {
125185
(event: "update:step", value: EmailStep): void;
126186
}
127187
188+
interface EmailAccountBaseState {
189+
email_account_name: string;
190+
email_id: string;
191+
service: string;
192+
enable_incoming: boolean;
193+
enable_outgoing: boolean;
194+
default_incoming: boolean;
195+
default_outgoing: boolean;
196+
}
197+
198+
interface EmailAccountProviderAuthState extends EmailAccountBaseState {
199+
password?: string;
200+
api_key?: string;
201+
api_secret?: string;
202+
frappe_mail_site?: string;
203+
}
204+
205+
type CustomEmailAccountState = {
206+
domain?: string;
207+
email_server?: string;
208+
incoming_port?: string | number;
209+
smtp_server?: string;
210+
smtp_port?: string | number;
211+
use_ssl?: boolean | number;
212+
use_starttls?: boolean | number;
213+
use_tls?: boolean | number;
214+
use_ssl_for_outgoing?: boolean | number;
215+
validate_ssl_certificate?: boolean | number;
216+
validate_ssl_certificate_for_outgoing?: boolean | number;
217+
attachment_limit?: string | number;
218+
append_emails_to_sent_folder?: boolean | number;
219+
sent_folder_name?: string;
220+
};
221+
128222
const emit = defineEmits<E>();
129223
130224
const { updateOnboardingStep } = useOnboarding("helpdesk");
131225
132-
const state: Reactive<EmailAccount> = reactive({
226+
const state = reactive<EmailAccountProviderAuthState>({
133227
service: "",
134228
email_account_name: "",
135229
email_id: "",
@@ -143,23 +237,74 @@ const state: Reactive<EmailAccount> = reactive({
143237
default_outgoing: false,
144238
});
145239
146-
const selectedService: Ref<EmailService> = ref(null);
147-
const fields = computed(() =>
148-
selectedService.value.custom ? customProviderFields : popularProviderFields
240+
const getDefaultCustomState = (): CustomEmailAccountState => ({
241+
domain: "",
242+
email_server: "",
243+
smtp_server: "",
244+
incoming_port: "",
245+
smtp_port: "",
246+
use_ssl: false,
247+
use_starttls: false,
248+
use_tls: false,
249+
use_ssl_for_outgoing: false,
250+
validate_ssl_certificate: true,
251+
validate_ssl_certificate_for_outgoing: true,
252+
attachment_limit: "",
253+
append_emails_to_sent_folder: false,
254+
sent_folder_name: "",
255+
});
256+
257+
const customState = reactive<CustomEmailAccountState>(getDefaultCustomState());
258+
259+
function resetCustomState() {
260+
Object.assign(customState, getDefaultCustomState());
261+
}
262+
263+
const selectedService: Ref<EmailService | null> = ref(null);
264+
const fields = computed(() => {
265+
if (!selectedService.value) return [];
266+
if (selectedService.value.name === "Frappe Mail") {
267+
return frappeMailFields;
268+
}
269+
if (selectedService.value.custom) {
270+
return customProviderTopFields;
271+
}
272+
return popularProviderFields;
273+
});
274+
275+
const isCustomSelected = computed(
276+
() => selectedService.value?.name === "Custom"
277+
);
278+
279+
watch(
280+
() => customState.domain,
281+
async (val) => {
282+
if (state.service !== "Custom") return;
283+
if (!isCustomSelected.value || !val) return;
284+
try {
285+
const doc = await call("frappe.client.get", {
286+
doctype: "Email Domain",
287+
name: val,
288+
});
289+
syncAccountFieldsFromDomain(doc);
290+
} catch (err) {
291+
console.warn("Failed to load Email Domain", err);
292+
}
293+
}
149294
);
150295
151296
function handleSelect(service: EmailService) {
152297
selectedService.value = service;
153298
state.service = service.name;
299+
if (service.name !== "Custom") {
300+
resetCustomState();
301+
}
154302
}
155303
156304
const addEmailRes = createResource({
157305
url: "helpdesk.api.settings.email.create_email_account",
158-
makeParams: (val: EmailAccount) => {
159-
return {
160-
...val,
161-
};
162-
},
306+
makeParams: (val: EmailAccount) => ({ ...val }),
307+
163308
onSuccess: () => {
164309
toast.success(__("Email account created"));
165310
emit("update:step", "email-list");
@@ -173,10 +318,94 @@ const addEmailRes = createResource({
173318
174319
const error = ref<string | undefined>();
175320
function createEmailAccount() {
176-
error.value = validateInputs(state, selectedService.value.custom);
321+
const validationState = { ...state, ...customState };
322+
error.value = validateInputs(validationState, state.service);
177323
if (error.value) return;
178324
179-
addEmailRes.submit({ data: state });
325+
addEmailRes.submit({ data: buildCreatePayload() });
326+
}
327+
328+
function buildCreatePayload() {
329+
const commonPayload = {
330+
email_account_name: state.email_account_name,
331+
email_id: state.email_id,
332+
service: state.service,
333+
enable_incoming: state.enable_incoming,
334+
enable_outgoing: state.enable_outgoing,
335+
default_incoming: state.default_incoming,
336+
default_outgoing: state.default_outgoing,
337+
};
338+
339+
if (state.service === "Frappe Mail") {
340+
return {
341+
...commonPayload,
342+
frappe_mail_site: state.frappe_mail_site,
343+
api_key: state.api_key,
344+
api_secret: state.api_secret,
345+
};
346+
}
347+
348+
if (state.service === "Custom") {
349+
return {
350+
...commonPayload,
351+
password: state.password,
352+
...customState,
353+
};
354+
}
355+
356+
return {
357+
...commonPayload,
358+
password: state.password,
359+
};
360+
}
361+
362+
function syncAccountFieldsFromDomain(payload: Record<string, any>) {
363+
customState.email_server = payload.email_server ?? customState.email_server;
364+
customState.smtp_server = payload.smtp_server ?? customState.smtp_server;
365+
customState.incoming_port =
366+
payload.incoming_port ?? customState.incoming_port;
367+
customState.smtp_port = payload.smtp_port ?? customState.smtp_port;
368+
customState.use_ssl = Boolean(payload.use_ssl ?? customState.use_ssl);
369+
customState.use_starttls = Boolean(
370+
payload.use_starttls ?? customState.use_starttls
371+
);
372+
customState.use_tls = Boolean(payload.use_tls ?? customState.use_tls);
373+
customState.use_ssl_for_outgoing = Boolean(
374+
payload.use_ssl_for_outgoing ?? customState.use_ssl_for_outgoing
375+
);
376+
customState.validate_ssl_certificate = Boolean(
377+
payload.validate_ssl_certificate ?? customState.validate_ssl_certificate
378+
);
379+
customState.validate_ssl_certificate_for_outgoing = Boolean(
380+
payload.validate_ssl_certificate_for_outgoing ??
381+
customState.validate_ssl_certificate_for_outgoing
382+
);
383+
customState.append_emails_to_sent_folder = Boolean(
384+
payload.append_emails_to_sent_folder ??
385+
customState.append_emails_to_sent_folder
386+
);
387+
customState.sent_folder_name =
388+
payload.sent_folder_name || customState.sent_folder_name;
389+
customState.attachment_limit =
390+
payload.attachment_limit || customState.attachment_limit;
391+
}
392+
393+
function handleCreateDomainClick(value: any, close?: () => void) {
394+
close?.();
395+
window.open(
396+
"/desk/email-domain/new-email-domain",
397+
"_blank",
398+
"noopener,noreferrer"
399+
);
400+
}
401+
402+
function handleEditDomainClick() {
403+
if (!customState.domain) return;
404+
window.open(
405+
`/desk/email-domain/${encodeURIComponent(customState.domain)}`,
406+
"_blank",
407+
"noopener,noreferrer"
408+
);
180409
}
181410
</script>
182411

0 commit comments

Comments
 (0)