Merge branch 'develop' into fix/cw-4085

This commit is contained in:
Shivam Mishra
2025-03-06 20:10:38 +05:30
committed by GitHub
575 changed files with 9687 additions and 3516 deletions
@@ -201,10 +201,10 @@ export default {
<style lang="scss" scoped>
.filter {
@apply bg-slate-50 dark:bg-slate-800 p-2 border border-solid border-slate-75 dark:border-slate-600 rounded-md mb-2;
@apply bg-n-slate-3 dark:bg-n-solid-3 p-2 border border-solid border-n-strong dark:border-n-strong rounded-md mb-2;
&.is-a-macro {
@apply mb-0 bg-white dark:bg-slate-700 p-0 border-0 rounded-none;
@apply mb-0 bg-n-slate-2 dark:bg-n-solid-3 p-0 border-0 rounded-none;
}
}
@@ -128,7 +128,7 @@ export default {
getInputErrorClass(errorMessage) {
return errorMessage
? 'bg-red-50 dark:bg-red-800/50 border-red-100 dark:border-red-700/50'
: 'bg-slate-50 dark:bg-slate-800 border-slate-75 dark:border-slate-700/50';
: 'bg-n-slate-3 dark:bg-n-solid-3 border-n-strong dark:border-n-strong';
},
},
};
@@ -14,7 +14,7 @@ export default {
</script>
<template>
<div class="pt-4 pb-0 px-8 border-b border-solid border-n-weak">
<div class="pt-4 pb-0 px-8 border-b border-solid border-n-weak/60">
<h2 class="text-2xl text-slate-800 dark:text-slate-100 mb-1 font-medium">
{{ headerTitle }}
</h2>
@@ -127,7 +127,7 @@ export default {
const uploadRef = ref(false);
const keyboardEvents = {
'Alt+KeyA': {
'$mod+Alt+KeyA': {
action: () => {
// TODO: This is really hacky, we need to replace the file picker component with
// a custom one, where the logic and the component markup is isolated.
@@ -85,7 +85,7 @@ export default {
</script>
<template>
<div class="flex justify-between h-[52px] gap-2 ltr:pl-3 rtl:pr-3">
<div class="flex justify-between h-[3.25rem] gap-2 ltr:pl-3 rtl:pr-3">
<EditorModeToggle
:mode="mode"
class="mt-3"
@@ -411,7 +411,7 @@ export default {
}
.checkbox-wrapper {
@apply h-10 w-10 flex items-center justify-center rounded-full cursor-pointer mt-4;
@apply flex items-center justify-center rounded-full cursor-pointer mt-4;
input[type='checkbox'] {
@apply m-0 cursor-pointer;
@@ -53,7 +53,7 @@ const showCopilotTab = computed(() =>
<template>
<div
class="ltr:border-l rtl:border-r border-n-weak h-full overflow-hidden z-10 min-w-[320px] w-[320px] 2xl:min-w-96 2xl:w-96 flex flex-col bg-n-background"
class="ltr:border-l rtl:border-r border-n-weak h-full overflow-hidden z-10 w-80 min-w-80 2xl:min-w-96 2xl:w-96 flex flex-col bg-n-background"
>
<div v-if="showCopilotTab" class="p-2">
<TabBar
@@ -2,11 +2,13 @@
import { reactive, computed, onMounted, ref } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { useI18n } from 'vue-i18n';
import { useTrack } from 'dashboard/composables';
import { useAlert } from 'dashboard/composables';
import LinearAPI from 'dashboard/api/integrations/linear';
import validations from './validations';
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
import SearchableDropdown from './SearchableDropdown.vue';
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
const props = defineProps({
conversationId: {
@@ -45,7 +47,7 @@ const statusDesiredOrder = [
];
const isCreating = ref(false);
const inputStyles = { borderRadius: '12px', fontSize: '14px' };
const inputStyles = { borderRadius: '0.75rem', fontSize: '0.875rem' };
const formState = reactive({
title: '',
@@ -188,6 +190,7 @@ const createIssue = async () => {
const { id: issueId } = response.data;
await LinearAPI.link_issue(props.conversationId, issueId, props.title);
useAlert(t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_SUCCESS'));
useTrack(LINEAR_EVENTS.CREATE_ISSUE);
onClose();
} catch (error) {
const errorMessage = parseLinearAPIErrorResponse(
@@ -209,7 +212,7 @@ onMounted(getTeams);
v-model="formState.title"
:class="{ error: v$.title.$error }"
class="w-full"
:styles="{ ...inputStyles, padding: '6px 12px' }"
:styles="{ ...inputStyles, padding: '0.375rem 0.75rem' }"
:label="$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TITLE.LABEL')"
:placeholder="
$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TITLE.PLACEHOLDER')
@@ -221,7 +224,7 @@ onMounted(getTeams);
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.DESCRIPTION.LABEL') }}
<textarea
v-model="formState.description"
:style="{ ...inputStyles, padding: '8px 12px' }"
:style="{ ...inputStyles, padding: '0.5rem 0.75rem' }"
rows="3"
class="text-sm"
:placeholder="
@@ -1,11 +1,13 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useTrack } from 'dashboard/composables';
import { useAlert } from 'dashboard/composables';
import LinearAPI from 'dashboard/api/integrations/linear';
import FilterButton from 'dashboard/components/ui/Dropdown/DropdownButton.vue';
import FilterListDropdown from 'dashboard/components/ui/Dropdown/DropdownList.vue';
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
const props = defineProps({
conversationId: {
@@ -85,6 +87,7 @@ const linkIssue = async () => {
searchQuery.value = '';
issues.value = [];
onClose();
useTrack(LINEAR_EVENTS.LINK_ISSUE);
} catch (error) {
const errorMessage = parseLinearAPIErrorResponse(
error,
@@ -6,6 +6,8 @@ import { useI18n } from 'vue-i18n';
import LinearAPI from 'dashboard/api/integrations/linear';
import CreateOrLinkIssue from './CreateOrLinkIssue.vue';
import Issue from './Issue.vue';
import { useTrack } from 'dashboard/composables';
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
const props = defineProps({
@@ -56,6 +58,7 @@ const unlinkIssue = async linkId => {
try {
isUnlinking.value = true;
await LinearAPI.unlinkIssue(linkId);
useTrack(LINEAR_EVENTS.UNLINK_ISSUE);
linkedIssue.value = null;
useAlert(t('INTEGRATION_SETTINGS.LINEAR.UNLINK.SUCCESS'));
} catch (error) {
@@ -1,41 +1,34 @@
<script setup>
import { ref, onMounted } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout';
import { SHORTCUT_KEYS } from './constants';
import { SHORTCUT_KEYS, KEYS } from './constants';
import {
LAYOUT_QWERTZ,
keysToModifyInQWERTZ,
} from 'shared/helpers/KeyboardHelpers';
import Hotkey from 'dashboard/components/base/Hotkey.vue';
defineProps({
show: {
type: Boolean,
default: false,
},
});
defineProps({ show: Boolean });
defineEmits(['close']);
const { t } = useI18n();
const shortcutKeys = SHORTCUT_KEYS;
const currentLayout = ref(null);
const title = item => t(`KEYBOARD_SHORTCUTS.TITLE.${item.label}`);
const title = computed(
() => item => t(`KEYBOARD_SHORTCUTS.TITLE.${item.label}`)
);
// Added this function to check if the keySet needs a shift key
// This is used to display the shift key in the modal
// If the current layout is QWERTZ and the keySet contains a key that needs a shift key
// If layout is QWERTZ then we add the Shift+keysToModify to fix an known issue
// https://github.com/chatwoot/chatwoot/issues/9492
const needsShiftKey = keySet => {
return (
const needsShiftKey = computed(
() => keySet =>
currentLayout.value === LAYOUT_QWERTZ &&
keySet.some(key => keysToModifyInQWERTZ.has(key))
);
};
);
onMounted(async () => {
currentLayout.value = await useDetectKeyboardLayout();
@@ -55,80 +48,46 @@ onMounted(async () => {
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
{{ KEYS.WIN }}
</Hotkey>
<Hotkey custom-class="min-h-[28px] min-w-[36px] key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
{{ KEYS.SLASH }}
</Hotkey>
</div>
</div>
</div>
<div class="grid grid-cols-2 px-8 pt-0 pb-8 gap-x-5 gap-y-3">
<div class="flex justify-between items-center min-w-[25rem]">
<h5 class="text-sm text-slate-800 dark:text-slate-100">
{{ $t('KEYBOARD_SHORTCUTS.TITLE.OPEN_CONVERSATION') }}
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<div class="flex gap-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="min-h-[28px] w-9 key"> {{ 'J' }} </Hotkey>
<span
class="flex items-center text-sm font-semibold text-slate-800 dark:text-slate-100"
>
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
</span>
</div>
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="w-9 key"> {{ 'K' }} </Hotkey>
</div>
</div>
<div class="flex justify-between items-center min-w-[25rem]">
<h5 class="text-sm text-slate-800 dark:text-slate-100">
{{ $t('KEYBOARD_SHORTCUTS.TITLE.RESOLVE_AND_NEXT') }}
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
</Hotkey>
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="w-9 key"> {{ 'E' }} </Hotkey>
</div>
</div>
<div
v-for="shortcutKey in shortcutKeys"
:key="shortcutKey.id"
v-for="shortcut in SHORTCUT_KEYS"
:key="shortcut.id"
class="flex justify-between items-center min-w-[25rem]"
>
<h5 class="text-sm text-slate-800 min-w-[36px] dark:text-slate-100">
{{ title(shortcutKey) }}
{{ title(shortcut) }}
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey
v-if="needsShiftKey(shortcutKey.keySet)"
custom-class="min-h-[28px] min-w-[36px] key"
>
{{ 'Shift' }}
</Hotkey>
<Hotkey
:class="{ 'min-w-[60px]': shortcutKey.firstKey !== 'Up' }"
custom-class="min-h-[28px] normal-case key"
>
{{ shortcutKey.firstKey }}
</Hotkey>
<Hotkey
:class="{ 'normal-case': shortcutKey.secondKey === 'Down' }"
custom-class="min-h-[28px] min-w-[36px] key"
>
{{ shortcutKey.secondKey }}
</Hotkey>
<template v-if="needsShiftKey(shortcut.keySet)">
<Hotkey custom-class="min-h-[28px] min-w-[36px] key">
{{ KEYS.SHIFT }}
</Hotkey>
</template>
<template v-for="(key, index) in shortcut.displayKeys" :key="index">
<template v-if="key !== KEYS.SLASH">
<Hotkey
custom-class="min-h-[28px] min-w-[36px] key normal-case"
>
{{ key }}
</Hotkey>
</template>
<span
v-else
class="flex items-center text-sm font-semibold text-slate-800 dark:text-slate-100"
>
{{ key }}
</span>
</template>
</div>
</div>
</div>
@@ -1,86 +1,95 @@
export const KEYS = {
ALT: 'Alt / ⌥',
WIN: 'Win / ⌘',
SHIFT: 'Shift',
SLASH: '/',
UP: 'Up',
DOWN: 'Down',
};
export const SHORTCUT_KEYS = [
{
id: 1,
label: 'NAVIGATE_DROPDOWN',
firstKey: 'Up',
secondKey: 'Down',
keySet: ['ArrowUp', 'ArrowDown'],
label: 'OPEN_CONVERSATION',
displayKeys: [KEYS.ALT, 'J', KEYS.SLASH, KEYS.ALT, 'K'],
keySet: ['Alt+KeyJ', 'Alt+KeyK'],
},
{
id: 2,
label: 'RESOLVE_CONVERSATION',
firstKey: 'Alt / ⌥',
secondKey: 'E',
keySet: ['Alt+KeyE'],
label: 'RESOLVE_AND_NEXT',
displayKeys: [KEYS.WIN, KEYS.ALT, 'E'],
keySet: ['$mod+Alt+KeyE'],
},
{
id: 3,
label: 'GO_TO_CONVERSATION_DASHBOARD',
firstKey: 'Alt / ⌥',
secondKey: 'C',
keySet: ['Alt+KeyC'],
label: 'NAVIGATE_DROPDOWN',
displayKeys: [KEYS.UP, KEYS.DOWN],
keySet: ['ArrowUp', 'ArrowDown'],
},
{
id: 4,
label: 'ADD_ATTACHMENT',
firstKey: 'Alt / ⌥',
secondKey: 'A',
keySet: ['Alt+KeyA'],
label: 'RESOLVE_CONVERSATION',
displayKeys: [KEYS.ALT, 'E'],
keySet: ['Alt+KeyE'],
},
{
id: 5,
label: 'GO_TO_CONTACTS_DASHBOARD',
firstKey: 'Alt / ⌥',
secondKey: 'V',
keySet: ['Alt+KeyV'],
label: 'GO_TO_CONVERSATION_DASHBOARD',
displayKeys: [KEYS.ALT, 'C'],
keySet: ['Alt+KeyC'],
},
{
id: 6,
label: 'TOGGLE_SIDEBAR',
firstKey: 'Alt / ⌥',
secondKey: 'O',
keySet: ['Alt+KeyO'],
label: 'ADD_ATTACHMENT',
displayKeys: [KEYS.WIN, KEYS.ALT, 'A'],
keySet: ['$mod+Alt+KeyA'],
},
{
id: 7,
label: 'GO_TO_REPORTS_SIDEBAR',
firstKey: 'Alt / ⌥',
secondKey: 'R',
keySet: ['Alt+KeyR'],
label: 'GO_TO_CONTACTS_DASHBOARD',
displayKeys: [KEYS.ALT, 'V'],
keySet: ['Alt+KeyV'],
},
{
id: 8,
label: 'MOVE_TO_NEXT_TAB',
firstKey: 'Alt / ⌥',
secondKey: 'N',
keySet: ['Alt+KeyN'],
label: 'TOGGLE_SIDEBAR',
displayKeys: [KEYS.ALT, 'O'],
keySet: ['Alt+KeyO'],
},
{
id: 9,
label: 'GO_TO_SETTINGS',
firstKey: 'Alt / ⌥',
secondKey: 'S',
keySet: ['Alt+KeyS'],
label: 'GO_TO_REPORTS_SIDEBAR',
displayKeys: [KEYS.ALT, 'R'],
keySet: ['Alt+KeyR'],
},
{
id: 10,
label: 'MOVE_TO_NEXT_TAB',
displayKeys: [KEYS.ALT, 'N'],
keySet: ['Alt+KeyN'],
},
{
id: 11,
label: 'SWITCH_TO_PRIVATE_NOTE',
firstKey: 'Alt / ⌥',
secondKey: 'P',
keySet: ['Alt+KeyP'],
label: 'GO_TO_SETTINGS',
displayKeys: [KEYS.ALT, 'S'],
keySet: ['Alt+KeyS'],
},
{
id: 12,
label: 'SWITCH_TO_REPLY',
firstKey: 'Alt / ⌥',
secondKey: 'L',
keySet: ['Alt+KeyL'],
label: 'SWITCH_TO_PRIVATE_NOTE',
displayKeys: [KEYS.ALT, 'P'],
keySet: ['Alt+KeyP'],
},
{
id: 13,
label: 'SWITCH_TO_REPLY',
displayKeys: [KEYS.ALT, 'L'],
keySet: ['Alt+KeyL'],
},
{
id: 14,
label: 'TOGGLE_SNOOZE_DROPDOWN',
firstKey: 'Alt / ⌥',
secondKey: 'M',
displayKeys: [KEYS.ALT, 'M'],
keySet: ['Alt+KeyM'],
},
];