Merge branch 'develop' into feat/whatsapp-embedded-signup

This commit is contained in:
Muhsin Keloth
2025-06-10 14:29:55 +05:30
committed by GitHub
12 changed files with 220 additions and 61 deletions
@@ -71,6 +71,7 @@ class OauthCallbackController < ApplicationController
def create_channel_with_inbox
ActiveRecord::Base.transaction do
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
account.inboxes.create!(
account: account,
channel: channel_email,
+16 -1
View File
@@ -1,3 +1,4 @@
// scss-lint:disable SpaceAfterPropertyColon
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@@ -7,7 +8,21 @@
html,
body {
font-family: 'InterDisplay', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
font-family:
'InterDisplay',
-apple-system,
system-ui,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Tahoma,
Arial,
sans-serif,
'Noto Sans',
'Apple Color Emoji',
'Segoe UI Emoji',
'Noto Color Emoji';
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
height: 100%;
@@ -37,7 +37,7 @@ export default {
}
if (el.tag === 'h2') {
if (this.h1Count > 0) {
return 'ml-2';
return 'ltr:ml-2 rtl:mr-2';
}
return '';
}
@@ -46,7 +46,7 @@ export default {
if (!this.h1Count && !this.h2Count) {
return '';
}
return 'ml-5';
return 'ltr:ml-5 rtl:mr-5';
}
return '';
@@ -94,17 +94,19 @@ export default {
</script>
<template>
<div class="hidden lg:block flex-1 py-6 scroll-mt-24 pl-4 sticky top-24">
<div
class="hidden lg:block flex-1 py-6 scroll-mt-24 ltr:pl-4 rtl:pr-4 sticky top-24"
>
<div v-if="rows.length > 0" class="py-2 overflow-auto">
<nav class="max-w-2xl">
<ol
role="list"
class="flex flex-col gap-2 text-base border-l-2 border-solid border-slate-100 dark:border-slate-800"
class="flex flex-col gap-2 text-base ltr:border-l-2 rtl:border-r-2 border-solid border-slate-100 dark:border-slate-800"
>
<li
v-for="element in rows"
:key="element.slug"
class="leading-6 border-l-2 relative -left-0.5 border-solid"
class="leading-6 ltr:border-l-2 rtl:border-r-2 relative ltr:-left-0.5 rtl:-right-0.5 border-solid"
:class="elementBorderStyles(element)"
>
<p class="py-1 px-3" :class="getClassName(element)">
+10
View File
@@ -8,6 +8,7 @@ import slugifyWithCounter from '@sindresorhus/slugify';
import PublicArticleSearch from './components/PublicArticleSearch.vue';
import TableOfContents from './components/TableOfContents.vue';
import { initializeTheme } from './portalThemeHelper.js';
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
export const getHeadingsfromTheArticle = () => {
const rows = [];
@@ -114,10 +115,19 @@ export const InitializationHelpers = {
});
},
setDirectionAttribute: () => {
const portalElement = document.getElementById('portal');
if (!portalElement) return;
const locale = document.querySelector('.locale-switcher')?.value;
portalElement.dir = locale && getLanguageDirection(locale) ? 'rtl' : 'ltr';
},
initializeThemesInPortal: initializeTheme,
initialize: () => {
openExternalLinksInNewTab();
InitializationHelpers.setDirectionAttribute();
if (window.portalConfig.isPlainLayoutEnabled === 'true') {
InitializationHelpers.appendPlainParamToURLs();
} else {
@@ -4,7 +4,8 @@ class AgentNotifications::ConversationNotificationsMailer < ApplicationMailer
@agent = agent
@conversation = conversation
subject = "#{@agent.available_name}, A new conversation [ID - #{@conversation.display_id}] has been created in #{@conversation.inbox&.name}."
inbox_name = @conversation.inbox&.sanitized_name
subject = "#{@agent.available_name}, A new conversation [ID - #{@conversation.display_id}] has been created in #{inbox_name}."
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
send_mail_with_liquid(to: @agent.email, subject: subject) and return
end
+1 -1
View File
@@ -104,7 +104,7 @@ class ConversationReplyMailer < ApplicationMailer
end
def business_name
@inbox.business_name || @inbox.name
@inbox.business_name || @inbox.sanitized_name
end
def from_email
+26 -2
View File
@@ -47,8 +47,6 @@ class Inbox < ApplicationRecord
# Not allowing characters:
validates :name, presence: true
validates :name, if: :check_channel_type?, format: { with: %r{^^\b[^/\\<>@]*\b$}, multiline: true,
message: I18n.t('errors.inboxes.validations.name') }
validates :account_id, presence: true
validates :timezone, inclusion: { in: TZInfo::Timezone.all_identifiers }
validates :out_of_office_message, length: { maximum: Limits::OUT_OF_OFFICE_MESSAGE_MAX_LENGTH }
@@ -99,6 +97,16 @@ class Inbox < ApplicationRecord
update_account_cache
end
# Sanitizes inbox name for balanced email provider compatibility
# ALLOWS: /'._- and Unicode letters/numbers/emojis
# REMOVES: Forbidden chars (\<>@") + spam-trigger symbols (!#$%&*+=?^`{|}~)
def sanitized_name
return default_name_for_blank_name if name.blank?
sanitized = apply_sanitization_rules(name)
sanitized.blank? && email? ? display_name_from_email : sanitized
end
def sms?
channel_type == 'Channel::Sms'
end
@@ -178,6 +186,22 @@ class Inbox < ApplicationRecord
private
def default_name_for_blank_name
email? ? display_name_from_email : ''
end
def apply_sanitization_rules(name)
name.gsub(/[\\<>@"!#$%&*+=?^`{|}~]/, '') # Remove forbidden chars
.gsub(/[\x00-\x1F\x7F]/, ' ') # Replace control chars with spaces
.gsub(/\A[[:punct:]]+|[[:punct:]]+\z/, '') # Remove leading/trailing punctuation
.gsub(/\s+/, ' ') # Normalize spaces
.strip
end
def display_name_from_email
channel.email.split('@').first.parameterize.titleize
end
def dispatch_create_event
return if ENV['ENABLE_INBOX_EVENTS'].blank?
@@ -1,10 +1,10 @@
<% author_count = category.articles.published.order(position: :asc).map(&:author).uniq.size %>
<% if author_count > 0 %>
<div class="flex flex-row items-center gap-1">
<div class="flex flex-row items-center -space-x-2">
<% category.articles.published.order(position: :asc).map(&:author).uniq.take(3).each do |author| %>
<%= render "public/api/v1/portals/thumbnail", author: author, size: 5 %>
<% end %>
<div class="flex items-center ltr:flex-row rtl:flex-row-reverse -space-x-2">
<% category.articles.published.order(position: :asc).map(&:author).uniq.take(3).each do |author| %>
<%= render "public/api/v1/portals/thumbnail", author: author, size: 5 %>
<% end %>
</div>
<% first_author = category.articles.published.order(position: :asc).map(&:author).uniq.first&.name || '' %>
@@ -3,7 +3,7 @@
<div class="flex items-center w-full py-5 overflow-hidden">
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center h-10 text-lg font-semibold text-slate-900 dark:text-white">
<% if @portal.logo.present? %>
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 mr-2" />
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
<% end %>
<%= @portal.name %>
</a>
@@ -12,7 +12,7 @@
<%# Go to homepage link section %>
<div class="flex items-center justify-between gap-2 sm:gap-5">
<% if @portal.homepage_link %>
<div class="hidden px-1 py-2 ml-8 cursor-pointer border-l-1 border-slate-50 dark:border-slate-800 md:block">
<div class="hidden px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer md:block">
<div class="flex-grow flex-shrink-0">
<a id="header-action-button" target="_blank" rel="noopener noreferrer nofollow" href="<%= @portal.homepage_link %>" class="flex flex-row items-center gap-1 text-sm font-medium whitespace-nowrap text-slate-800 dark:text-slate-100 stroke-slate-700 dark:stroke-slate-200">
<%= render partial: 'icons/redirect' %>
@@ -42,7 +42,7 @@
</div>
</button>
<%# Appearance dropdown section %>
<div id="appearance-dropdown" data-current-theme="<%= @theme_from_params %>" class="absolute flex-col w-32 h-auto bg-white border border-solid rounded dark:bg-slate-900 top-9 right-1 border-slate-100 dark:border-slate-800" aria-hidden="true" style="display: none;" data-dropdown="appearance-dropdown">
<div id="appearance-dropdown" data-current-theme="<%= @theme_from_params %>" class="absolute flex-col w-32 h-auto bg-white border border-solid rounded dark:bg-slate-900 top-9 ltr:right-1 rtl:left-1 border-slate-100 dark:border-slate-800" aria-hidden="true" style="display: none;" data-dropdown="appearance-dropdown">
<button id="toggle-theme-button" data-theme="system" class="flex flex-row items-center justify-between gap-1 px-2 py-2 border-b border-solid border-slate-100 dark:border-slate-800 stroke-slate-700 dark:stroke-slate-200 text-slate-800 dark:text-slate-100">
<div class="flex flex-row items-center gap-1">
<%= render partial: 'icons/monitor' %>