mirror of
https://github.com/hanzoai/helpdesk.git
synced 2026-10-12 16:56:02 +00:00
Merge remote-tracking branch 'upstream/develop' into fix/agent-landing-page-bugs
This commit is contained in:
+2
-2
@@ -18,7 +18,7 @@
|
||||
"@vueuse/core": "^13.8.0",
|
||||
"@vueuse/integrations": "^13.8.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"frappe-ui": "0.1.269",
|
||||
"frappe-ui": "0.1.271",
|
||||
"gemoji": "^8.1.0",
|
||||
"mime": "^3.0.0",
|
||||
"pinia": "^2.0.33",
|
||||
@@ -43,4 +43,4 @@
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"prosemirror-model": "1.25.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -205,6 +205,13 @@ watch(iframeRef, (iframe) => {
|
||||
|
||||
iframe.style.height = parent.offsetHeight + 1 + "px";
|
||||
|
||||
// Clicks inside the iframe don't bubble to the parent document, popovers/dropdowns that close on outside-click never fire.
|
||||
iframe.contentDocument?.addEventListener("pointerdown", () => {
|
||||
document.dispatchEvent(
|
||||
new PointerEvent("pointerdown", { bubbles: true })
|
||||
);
|
||||
});
|
||||
|
||||
const replyCollapsers = emailContent.querySelectorAll(".replyCollapser");
|
||||
if (replyCollapsers.length) {
|
||||
replyCollapsers.forEach((replyCollapser) => {
|
||||
|
||||
@@ -442,9 +442,8 @@ onMounted(() => {
|
||||
|
||||
function handleSelectAll(e: KeyboardEvent) {
|
||||
const active = document.activeElement;
|
||||
const editorDom = editorRef.value?.editor?.view?.dom as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const editorContext = editorRef.value?.editor;
|
||||
const editorDom = editorContext?.view?.dom as HTMLElement | undefined;
|
||||
const quotedEl = quotedContentRef.value;
|
||||
const sel = window.getSelection();
|
||||
if (!sel || !editorDom) return;
|
||||
@@ -452,6 +451,7 @@ function handleSelectAll(e: KeyboardEvent) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
editorContext?.commands.selectAll();
|
||||
sel.removeAllRanges();
|
||||
const range = document.createRange();
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
params: { [options.rowRoute?.prop]: row.name },
|
||||
query: { view: route.query?.view },
|
||||
}),
|
||||
onRowClick: (row) => emit('rowClick', row.name),
|
||||
emptyState,
|
||||
}"
|
||||
>
|
||||
@@ -163,7 +162,6 @@ import {
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
nextTick,
|
||||
onMounted,
|
||||
provide,
|
||||
reactive,
|
||||
@@ -326,9 +324,6 @@ const list = createResource({
|
||||
onSuccess: (data) => {
|
||||
list.params = defaultParams;
|
||||
columns.value = data.columns;
|
||||
nextTick(() => {
|
||||
document.querySelector(".list-rows")?.focus();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -774,8 +769,3 @@ onMounted(async () => {
|
||||
|
||||
defineExpose(exposeFunctions);
|
||||
</script>
|
||||
<style scoped>
|
||||
.list-rows:focus {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
@@ -81,6 +81,13 @@ const props = defineProps({
|
||||
const hide = ref(props.hideLabel);
|
||||
const opened = ref(props.opened);
|
||||
|
||||
watch(
|
||||
() => props.opened,
|
||||
(val) => {
|
||||
opened.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
function toggle() {
|
||||
opened.value = !opened.value;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class="flex w-52 shrink-0 flex-col bg-gray-50 p-1 overflow-y-auto hide-scrollbar"
|
||||
>
|
||||
<h1
|
||||
class="h-7.5 px-2 py-[7px] my-[3px] flex cursor-pointer gap-1.5 text-base text-ink-gray-5 transition-all duration-300 ease-in-out"
|
||||
class="h-7.5 px-2 py-[7px] my-[3px] flex cursor-pointer gap-1.5 text-base text-ink-gray-5 transition-all duration-300 ease-in-out truncate"
|
||||
>
|
||||
{{ __("My Settings") }}
|
||||
</h1>
|
||||
@@ -21,7 +21,9 @@
|
||||
v-if="!tab.hideLabel"
|
||||
class="h-7.5 px-2 py-[7px] my-[3px] flex cursor-pointer gap-1.5 text-base text-ink-gray-5 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<span>{{ __(tab.label) }}</span>
|
||||
<Tooltip :text="__(tab.label)" placement="right">
|
||||
<span class="truncate">{{ __(tab.label) }}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<nav class="space-y-[3px] px-1">
|
||||
@@ -36,10 +38,15 @@
|
||||
]"
|
||||
@click="() => onTabChange(item)"
|
||||
>
|
||||
<component :is="item.icon" class="h-4 w-4 text-gray-700" />
|
||||
<span class="text-p-sm text-gray-800">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 text-gray-700 shrink-0"
|
||||
/>
|
||||
<Tooltip :text="__(item.label)" placement="right">
|
||||
<span class="text-p-sm text-gray-800 truncate">
|
||||
{{ __(item.label) }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -78,7 +85,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Dialog } from "frappe-ui";
|
||||
import { Dialog, Tooltip } from "frappe-ui";
|
||||
import { ModelRef, ref, watch } from "vue";
|
||||
import ConfirmDialog from "@/components/ConfirmDialog.vue";
|
||||
import {
|
||||
|
||||
@@ -33,14 +33,16 @@
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="-all ml-2 flex shrink-0 grow items-center justify-between text-sm duration-300 ease-in-out"
|
||||
class="-all ml-2 flex min-w-0 items-center justify-between text-sm duration-300 ease-in-out w-full"
|
||||
:class="{
|
||||
'opacity-100': isExpanded,
|
||||
'opacity-0': !isExpanded,
|
||||
'-z-50': !isExpanded,
|
||||
}"
|
||||
>
|
||||
{{ __(label) }}
|
||||
<Tooltip :text="__(label)" placement="right">
|
||||
<span class="truncate"> {{ __(label) }}</span>
|
||||
</Tooltip>
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="text-lg font-medium text-nowrap"
|
||||
class="text-lg font-medium text-nowrap truncate max-w-[200px] sm:max-w-none"
|
||||
:label="currentView.label"
|
||||
>
|
||||
<template #prefix>
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
:class="inputClasses"
|
||||
@click="() => !disabled && togglePopover()"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<slot name="prefix" />
|
||||
<span
|
||||
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
|
||||
v-if="selectedValue"
|
||||
>
|
||||
<span class="text-base leading-5 truncate" v-if="selectedValue">
|
||||
{{ displayValue(selectedValue) }}
|
||||
</span>
|
||||
<span class="text-base leading-5 text-gray-500" v-else>
|
||||
|
||||
@@ -50,7 +50,12 @@
|
||||
</template>
|
||||
</SidebarLink>
|
||||
</div>
|
||||
<div class="overflow-y-auto overflow-x-hidden">
|
||||
<div
|
||||
:class="[
|
||||
'overflow-y-auto overflow-x-hidden',
|
||||
!isExpanded && 'hide-scrollbar',
|
||||
]"
|
||||
>
|
||||
<div v-for="view in allViews" :key="view.label">
|
||||
<!-- <div
|
||||
v-if="!view.hideLabel && !isExpanded && view.views?.length"
|
||||
@@ -60,9 +65,9 @@
|
||||
<Section
|
||||
:label="view.label"
|
||||
:hideLabel="view.hideLabel"
|
||||
:opened="view.opened"
|
||||
:opened="isSectionOpen(view.label, view.opened)"
|
||||
>
|
||||
<template #header="{ opened, hide, toggle }">
|
||||
<template #header="{ opened, hide }">
|
||||
<div
|
||||
v-if="!hide"
|
||||
class="flex cursor-pointer gap-1.5 px-2 text-base mx-2 font-medium text-ink-gray-5 transition-all duration-300 ease-in-out"
|
||||
@@ -71,12 +76,12 @@
|
||||
? 'ml-0 h-0 overflow-hidden opacity-0'
|
||||
: 'pt-[11px] pb-2.5 w-auto opacity-100 '
|
||||
"
|
||||
@click="toggle()"
|
||||
@click="toggleSection(view.label, view.opened)"
|
||||
>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 text-ink-gray-9 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
:class="{ 'rotate-90': isSectionOpen(view.label, view.opened) }"
|
||||
/>
|
||||
<span>{{ __(view.label) }}</span>
|
||||
</div>
|
||||
@@ -212,7 +217,6 @@ import LucideBell from "~icons/lucide/bell";
|
||||
import FileText from "~icons/lucide/file-text";
|
||||
import Globe from "~icons/lucide/globe";
|
||||
import LucideKeyboard from "~icons/lucide/keyboard";
|
||||
import LucideLayoutDashboard from "~icons/lucide/layout-dashboard";
|
||||
import LucideMail from "~icons/lucide/mail";
|
||||
import MailOpen from "~icons/lucide/mail-open";
|
||||
import MessageCircle from "~icons/lucide/message-circle";
|
||||
@@ -245,6 +249,19 @@ const { pinnedViews, publicViews } = useView();
|
||||
|
||||
const isFCSite = ref(window.is_fc_site);
|
||||
|
||||
const sectionOpenState = ref<Record<string, boolean>>({});
|
||||
|
||||
function isSectionOpen(label: string, defaultOpen: boolean): boolean {
|
||||
if (!isExpanded.value) return true;
|
||||
if (label in sectionOpenState.value) return sectionOpenState.value[label];
|
||||
return defaultOpen;
|
||||
}
|
||||
|
||||
function toggleSection(label: string, defaultOpen: boolean) {
|
||||
const current = isSectionOpen(label, defaultOpen);
|
||||
sectionOpenState.value[label] = !current;
|
||||
}
|
||||
|
||||
const allViews = computed(() => {
|
||||
let items = isCustomerPortal.value
|
||||
? customerPortalSidebarOptions
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<!-- Optional Text -->
|
||||
<div v-if="activity.feedback_extra">
|
||||
<p class="mt-2 text-sm text-gray-700">{{ activity.feedback_extra }}</p>
|
||||
<p class="mt-2 text-p-sm text-gray-700">{{ activity.feedback_extra }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -94,7 +94,13 @@
|
||||
class="text-base text-gray-800 flex-1"
|
||||
:class="!field.value && 'text-ink-gray-4'"
|
||||
>
|
||||
<template v-if="field.value && dayjs(field.value).isValid()">
|
||||
<template
|
||||
v-if="
|
||||
field.value &&
|
||||
(field.fieldtype === 'Date' || field.fieldtype === 'Datetime') &&
|
||||
dayjs(field.value).isValid()
|
||||
"
|
||||
>
|
||||
{{ dateFormat(field.value, dateTooltipFormat) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -173,6 +173,7 @@ import {
|
||||
Tooltip,
|
||||
} from "frappe-ui";
|
||||
import { computed, h, inject } from "vue";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
|
||||
const props = defineProps({
|
||||
default_filters: {
|
||||
@@ -492,7 +493,7 @@ function updateValue(value, filter) {
|
||||
} else {
|
||||
filter.value = value;
|
||||
}
|
||||
apply();
|
||||
debouncedApply();
|
||||
}
|
||||
|
||||
function updateOperator(event, filter) {
|
||||
@@ -672,4 +673,8 @@ const timespanOptions = [
|
||||
value: "next year",
|
||||
},
|
||||
];
|
||||
|
||||
const debouncedApply = useDebounceFn(() => {
|
||||
apply();
|
||||
}, 500);
|
||||
</script>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
type="checkbox"
|
||||
:checked="props.value"
|
||||
@change.stop="updateFilter(filter, $event.target.checked)"
|
||||
class="w-44"
|
||||
class="w-36"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="filter.type === 'Select'"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer w-44"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer w-36"
|
||||
type="select"
|
||||
:model-value="props.value"
|
||||
:options="filter.options"
|
||||
@@ -22,11 +22,11 @@
|
||||
:doctype="filter.options"
|
||||
:placeholder="filter.label"
|
||||
@change="(data) => updateFilter(filter, data)"
|
||||
class="w-44"
|
||||
class="w-36"
|
||||
/>
|
||||
<component
|
||||
v-else-if="['Date', 'Datetime'].includes(filter.type)"
|
||||
class="border-none w-44"
|
||||
class="border-none w-36"
|
||||
:is="filter.type === 'Date' ? DatePicker : DateTimePicker"
|
||||
:value="props.value"
|
||||
@change="(v) => updateFilter(filter, v)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<FadedScrollableDiv
|
||||
class="flex flex-1 items-center -ml-1 overflow-x-scroll py-1 gap-2"
|
||||
class="quick-filters flex flex-1 items-center -ml-1 overflow-x-auto py-1 gap-2 pr-4"
|
||||
orientation="horizontal"
|
||||
v-if="!quickFilters.loading"
|
||||
>
|
||||
@@ -79,4 +79,12 @@ function getValue(quickFilter, filters) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped>
|
||||
.quick-filters {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
.quick-filters::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@apply break-normal max-w-none prose prose-code:break-all prose-code:whitespace-pre-wrap prose-img:border prose-img:rounded-lg prose-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.tiptap input[placeholder="Add caption"] {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -17,3 +18,31 @@ html,
|
||||
body {
|
||||
color: var(--ink-gray-9);
|
||||
}
|
||||
|
||||
/* Modern slim scrollbars */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--outline-gray-3) transparent;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--outline-gray-3);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--outline-gray-4);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "path";
|
||||
import type { PluginOption } from "vite";
|
||||
|
||||
interface LocalFrappeUIDevConfigParams {
|
||||
mode: string;
|
||||
rootDir: string;
|
||||
}
|
||||
|
||||
interface LocalFrappeUIDevConfig {
|
||||
useLocalFrappeUI: boolean;
|
||||
localFrappeUIPath: string;
|
||||
localFrappeUIAliases: Record<string, string>;
|
||||
}
|
||||
|
||||
interface ImportFrappeUIPluginParams {
|
||||
useLocalFrappeUI: boolean;
|
||||
}
|
||||
|
||||
type FrappeUIPluginFactory = (...args: any[]) => PluginOption;
|
||||
type FrappeUIPluginModule = { default: FrappeUIPluginFactory };
|
||||
|
||||
async function loadFrappeUIPluginModule(
|
||||
modulePath: string
|
||||
): Promise<FrappeUIPluginModule> {
|
||||
return (await import(modulePath)) as FrappeUIPluginModule;
|
||||
}
|
||||
|
||||
export function getLocalFrappeUIDevConfig({
|
||||
mode,
|
||||
rootDir,
|
||||
}: LocalFrappeUIDevConfigParams): LocalFrappeUIDevConfig {
|
||||
const isDev = mode === "development";
|
||||
const localFrappeUIPath = path.resolve(rootDir, "../frappe-ui");
|
||||
const useLocalFrappeUI =
|
||||
isDev && existsSync(path.join(localFrappeUIPath, "node_modules"));
|
||||
|
||||
if (isDev && existsSync(localFrappeUIPath) && !useLocalFrappeUI) {
|
||||
console.warn("⚠️ Local frappe-ui found but dependencies not installed.");
|
||||
console.warn(" Run: cd ../frappe-ui && yarn install");
|
||||
}
|
||||
|
||||
const localFrappeUIAliases: Record<string, string> = useLocalFrappeUI
|
||||
? {
|
||||
"frappe-ui/style.css": path.resolve(
|
||||
localFrappeUIPath,
|
||||
"src",
|
||||
"style.css"
|
||||
),
|
||||
"frappe-ui": localFrappeUIPath,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
useLocalFrappeUI,
|
||||
localFrappeUIPath,
|
||||
localFrappeUIAliases,
|
||||
};
|
||||
}
|
||||
|
||||
export async function importFrappeUIPlugin({
|
||||
useLocalFrappeUI,
|
||||
}: ImportFrappeUIPluginParams): Promise<FrappeUIPluginFactory> {
|
||||
const npmModulePath = "frappe-ui/vite";
|
||||
const modulePath = useLocalFrappeUI
|
||||
? "../frappe-ui/vite/index.js"
|
||||
: npmModulePath;
|
||||
|
||||
try {
|
||||
const module = await loadFrappeUIPluginModule(modulePath);
|
||||
return module.default as FrappeUIPluginFactory;
|
||||
} catch (error) {
|
||||
if (useLocalFrappeUI) {
|
||||
console.warn(
|
||||
"⚠️ Failed to import local frappe-ui plugin, falling back to npm package"
|
||||
);
|
||||
console.warn(
|
||||
" Error:",
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
const fallbackModule = await loadFrappeUIPluginModule(npmModulePath);
|
||||
return fallbackModule.default as FrappeUIPluginFactory;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
+108
-92
@@ -1,102 +1,118 @@
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import frappeui from "frappe-ui/vite";
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import {
|
||||
getLocalFrappeUIDevConfig,
|
||||
importFrappeUIPlugin,
|
||||
} from "./vite-helpers";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
frappeui({
|
||||
frappeProxy: true,
|
||||
lucideIcons: true,
|
||||
jinjaBootData: true,
|
||||
buildConfig: {
|
||||
outDir: `../helpdesk/public/desk`,
|
||||
emptyOutDir: true,
|
||||
indexHtmlPath: "../helpdesk/www/helpdesk/index.html",
|
||||
},
|
||||
frappeTypes: {
|
||||
input: {
|
||||
helpdesk: [
|
||||
"hd_ticket_status",
|
||||
"hd_ticket",
|
||||
"hd_service_holiday_list",
|
||||
"hd_service_level_agreement",
|
||||
"hd_agent",
|
||||
],
|
||||
frappe: ["assignment_rule"],
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
const { useLocalFrappeUI, localFrappeUIAliases } = getLocalFrappeUIDevConfig({
|
||||
mode,
|
||||
rootDir: __dirname,
|
||||
});
|
||||
|
||||
const frappeui = await importFrappeUIPlugin({ useLocalFrappeUI });
|
||||
const config = {
|
||||
plugins: [
|
||||
frappeui({
|
||||
frappeProxy: true,
|
||||
lucideIcons: true,
|
||||
jinjaBootData: true,
|
||||
buildConfig: {
|
||||
outDir: `../helpdesk/public/desk`,
|
||||
emptyOutDir: true,
|
||||
indexHtmlPath: "../helpdesk/www/helpdesk/index.html",
|
||||
},
|
||||
},
|
||||
}),
|
||||
frappeTypes: {
|
||||
input: {
|
||||
helpdesk: [
|
||||
"hd_ticket_status",
|
||||
"hd_ticket",
|
||||
"hd_service_holiday_list",
|
||||
"hd_service_level_agreement",
|
||||
"hd_agent",
|
||||
],
|
||||
frappe: ["assignment_rule"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
vue(),
|
||||
vueJsx(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
workbox: {
|
||||
cleanupOutdatedCaches: true,
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
},
|
||||
manifest: {
|
||||
display: "standalone",
|
||||
name: "Frappe Helpdesk",
|
||||
short_name: "Helpdesk",
|
||||
start_url: "/helpdesk",
|
||||
description:
|
||||
"Modern, Streamlined, Free and Open Source Customer Service Software",
|
||||
icons: [
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-192.maskable.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-192.maskable.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-512.maskable.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-512.maskable.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
"tailwind.config.js": path.resolve(__dirname, "tailwind.config.js"),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
"feather-icons",
|
||||
"tailwind.config.js",
|
||||
"prosemirror-state",
|
||||
"prosemirror-view",
|
||||
"prosemirror-gapcursor",
|
||||
"prosemirror-tables",
|
||||
"lowlight",
|
||||
"interactjs",
|
||||
vue(),
|
||||
vueJsx(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
workbox: {
|
||||
cleanupOutdatedCaches: true,
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
},
|
||||
manifest: {
|
||||
display: "standalone",
|
||||
name: "Frappe Helpdesk",
|
||||
short_name: "Helpdesk",
|
||||
start_url: "/helpdesk",
|
||||
description:
|
||||
"Modern, Streamlined, Free and Open Source Customer Service Software",
|
||||
icons: [
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-192.maskable.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-192.maskable.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-512.maskable.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/assets/helpdesk/desk/manifest/manifest-icon-512.maskable.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
exclude: ["frappe-ui"],
|
||||
},
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
fs: {
|
||||
allow: [".."],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
"tailwind.config.js": path.resolve(__dirname, "tailwind.config.js"),
|
||||
...localFrappeUIAliases,
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
"feather-icons",
|
||||
"tailwind.config.js",
|
||||
"prosemirror-state",
|
||||
"prosemirror-view",
|
||||
"prosemirror-gapcursor",
|
||||
"prosemirror-tables",
|
||||
"lowlight",
|
||||
"interactjs",
|
||||
],
|
||||
exclude: ["frappe-ui"],
|
||||
},
|
||||
};
|
||||
return config;
|
||||
});
|
||||
|
||||
+4
-4
@@ -2650,10 +2650,10 @@ fraction.js@^5.3.4:
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a"
|
||||
integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==
|
||||
|
||||
[email protected]69:
|
||||
version "0.1.269"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.269.tgz#d694cd23fe6eadbd7bb898bb3f46e0414c27f829"
|
||||
integrity sha512-1D4AW8cPoC7OIRqEzYnF169/r+v0EfVBvfCi/ovmk76FBPKY+qIVOzL+Vx/9TcYvJubc1tSU19F/jX177e3axA==
|
||||
[email protected]71:
|
||||
version "0.1.271"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.271.tgz#206aa52f4faa541da0cfdb2b32ae845e622b08e1"
|
||||
integrity sha512-H6FDKLynUCXxo3eIainJd6FjWrBtAZ3WZXVRM4tvLT3J7x7P5JjoNefB6YD8WZ+MQThKSwMC+/Kzg8vY2mc5XQ==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.7.4"
|
||||
"@floating-ui/vue" "^1.1.6"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.21.3"
|
||||
__version__ = "1.22.1"
|
||||
|
||||
+1
-7
@@ -607,12 +607,7 @@ def apply_datetime_filter(query, field, filter_value):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_assignments(
|
||||
doctype: str,
|
||||
name: str | int,
|
||||
assignees: list[str],
|
||||
ignore_permissions: bool = False,
|
||||
):
|
||||
def remove_assignments(doctype: str, name: str | int, assignees: list[str]):
|
||||
assignees = frappe.parse_json(assignees)
|
||||
|
||||
if not assignees:
|
||||
@@ -625,5 +620,4 @@ def remove_assignments(
|
||||
todo=None,
|
||||
assign_to=assign_to,
|
||||
status="Cancelled",
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
|
||||
@@ -43,6 +43,7 @@ def update_agent_role(user: str, new_role: str):
|
||||
"""
|
||||
Update the role of the user to Agent
|
||||
"""
|
||||
frappe.only_for(("Agent Manager", "System Manager"))
|
||||
|
||||
user_doc = frappe.get_doc("User", user)
|
||||
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
# Copyright (c) 2022, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from helpdesk.helpdesk.doctype.hd_agent.hd_agent import update_agent_role
|
||||
|
||||
|
||||
class TestHDAgent(unittest.TestCase):
|
||||
pass
|
||||
class TestHDAgent(FrappeTestCase):
|
||||
test_user = "[email protected]"
|
||||
|
||||
def setUp(self):
|
||||
if not frappe.db.exists("User", self.test_user):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": self.test_user,
|
||||
"first_name": "Test User",
|
||||
"send_welcome_email": 0,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
def test_unauthorized_role_update(self):
|
||||
frappe.set_user(self.test_user)
|
||||
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
update_agent_role(self.test_user, "System Manager")
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
@@ -686,6 +686,7 @@ def get_ticket_contact(ticket: str | int):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_recent_similar_tickets(ticket: str | int):
|
||||
frappe.has_permission("HD Ticket", "read", str(ticket), throw=True)
|
||||
if not frappe.db.exists("HD Ticket", ticket):
|
||||
return {"recent_tickets": [], "similar_tickets": []}
|
||||
|
||||
|
||||
@@ -572,6 +572,10 @@ class HDTicket(Document):
|
||||
bcc: str | None = None,
|
||||
attachments: list[str] = [],
|
||||
):
|
||||
if not is_agent():
|
||||
frappe.throw(
|
||||
_("You are not permitted to reply as an agent"), frappe.PermissionError
|
||||
)
|
||||
skip_email_workflow = self.skip_email_workflow()
|
||||
medium = "" if skip_email_workflow else "Email"
|
||||
subject = f"Re: {self.subject}"
|
||||
|
||||
@@ -709,3 +709,33 @@ class TestHDTicket(IntegrationTestCase):
|
||||
remove_holidays()
|
||||
frappe.db.set_single_value("HD Settings", "default_ticket_status", "Open")
|
||||
frappe.delete_doc("HD Ticket Status", "New", force=True)
|
||||
|
||||
def test_security_unauthorized_reply_via_agent(self):
|
||||
ticket = make_ticket()
|
||||
frappe.set_user(non_agent)
|
||||
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
ticket.reply_via_agent(message="Test unauthorized reply")
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_security_unauthorized_assign_agent(self):
|
||||
ticket = make_ticket()
|
||||
frappe.set_user(non_agent)
|
||||
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
ticket.assign_agent(agent)
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_security_info_disclosure_similar_tickets(self):
|
||||
from helpdesk.helpdesk.doctype.hd_ticket.api import get_recent_similar_tickets
|
||||
|
||||
ticket = make_ticket()
|
||||
|
||||
frappe.set_user(non_agent)
|
||||
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
get_recent_similar_tickets(ticket.name)
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
+1006
-396
File diff suppressed because it is too large
Load Diff
+1200
-590
File diff suppressed because it is too large
Load Diff
+1019
-409
File diff suppressed because it is too large
Load Diff
+1001
-391
File diff suppressed because it is too large
Load Diff
+1005
-395
File diff suppressed because it is too large
Load Diff
+1080
-470
File diff suppressed because it is too large
Load Diff
+1054
-444
File diff suppressed because it is too large
Load Diff
+1031
-421
File diff suppressed because it is too large
Load Diff
+1006
-396
File diff suppressed because it is too large
Load Diff
+1193
-583
File diff suppressed because it is too large
Load Diff
+1008
-398
File diff suppressed because it is too large
Load Diff
+1009
-399
File diff suppressed because it is too large
Load Diff
+1011
-401
File diff suppressed because it is too large
Load Diff
+696
-393
File diff suppressed because it is too large
Load Diff
+1001
-391
File diff suppressed because it is too large
Load Diff
+1003
-393
File diff suppressed because it is too large
Load Diff
+1014
-404
File diff suppressed because it is too large
Load Diff
+1004
-394
File diff suppressed because it is too large
Load Diff
+1009
-399
File diff suppressed because it is too large
Load Diff
+1016
-406
File diff suppressed because it is too large
Load Diff
+1079
-469
File diff suppressed because it is too large
Load Diff
+1019
-409
File diff suppressed because it is too large
Load Diff
+1075
-465
File diff suppressed because it is too large
Load Diff
+1075
-465
File diff suppressed because it is too large
Load Diff
+1080
-470
File diff suppressed because it is too large
Load Diff
+1009
-399
File diff suppressed because it is too large
Load Diff
+1008
-398
File diff suppressed because it is too large
Load Diff
+1038
-428
File diff suppressed because it is too large
Load Diff
+1034
-424
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,50 @@ from frappe.desk.form.assign_to import add as add_assign
|
||||
AUTHOR_EMAIl = "[email protected]"
|
||||
AUTHOR_NAME = "John Doe"
|
||||
CONTENT = """
|
||||
<p>
|
||||
Hi 👋
|
||||
<div style="font-family: 'Segoe UI', sans-serif; font-size: 15px; line-height: 1.8; color: #374151; max-width: 560px; margin: 0 auto;">
|
||||
Hey {{ first_name }} 👋,
|
||||
<br><br>
|
||||
I'm glad you decided to try Helpdesk! We're working hard to build a better way for teams
|
||||
to communicate and serve customers well.
|
||||
We thought we'd use this space to give you a quick walkthrough while you're here. 🙂
|
||||
<br><br>
|
||||
You can get started right away by setting up a support email. This will help you see what
|
||||
your support will look like with Helpdesk!
|
||||
This is a sample ticket we created to show you how Frappe Helpdesk works.
|
||||
<br><br>
|
||||
If you face any issues, please reach out to us via <a href="https://support.frappe.io/helpdesk">
|
||||
https://support.frappe.io/helpdesk</a>
|
||||
<br><br>
|
||||
Best,
|
||||
Getting started is easy. In the main <b>Ticket activity area</b>, when you reply to a ticket your response shows up directly below in the activity thread. This way you can keep track of all your ticket activities in one single place. ✨
|
||||
<br>
|
||||
Ritvik Sardana | Frappe Helpdesk.
|
||||
<img src="/assets/helpdesk/desk/ticketActivity.png" style="width: 100%; max-width: 560px; margin: 16px 0; border-radius: 10px; display: block;">
|
||||
<br>
|
||||
When it's time to follow up with a user, just click <b>Reply</b>. Your message will be sent directly to their inbox, keeping the conversation flowing smoothly.
|
||||
<br>
|
||||
<video autoplay loop muted src="/assets/helpdesk/desk/videos/mailVideo.mp4" style="width: 100%; max-width: 560px; margin: 16px 0; border-radius: 10px; display: block;"></video>
|
||||
<br>
|
||||
Want to discuss a ticket with your team internally? Utilize the <b>Comment</b> feature to leave notes that are visible only to your colleagues, ensuring that internal communication remains clear, organized, and private.<br>
|
||||
<video autoplay loop muted src="/assets/helpdesk/desk/videos/commentVideo.mp4" style="width: 100%; max-width: 560px; margin: 16px 0; border-radius: 10px; display: block;"></video>
|
||||
<br>
|
||||
In the <b>Ticket Sidebar</b>, you can manage the ticket. You can assign it to yourself or a teammate, set the priority, ticket type and also update the ticket status.
|
||||
<br>
|
||||
<video autoplay loop muted src="/assets/helpdesk/desk/videos/sidebarVideo.mp4" style="width: 100%; max-width: 560px; margin: 16px 0; border-radius: 10px; display: block;"></video>
|
||||
<br>
|
||||
Inside the <b>Ticket Header</b>, you can view the first response and resolution timers. Additionally, you can also see the source of the ticket, whether it came from email or the support portal.
|
||||
<br>
|
||||
<img src="/assets/helpdesk/desk/timers.png" style="width: 100%; max-width: 560px; margin: 16px 0; border-radius: 10px; display: block;">
|
||||
<br><br>
|
||||
That's pretty much how it works.
|
||||
<br><br>
|
||||
What you can do next:<br>
|
||||
<ul style="padding-left: 20px; margin: 0;">
|
||||
<li>Connect your support email to start receiving real tickets.</li>
|
||||
<li>Invite your team and auto-assign tickets using assignment rules.</li>
|
||||
<li>Set up SLAs to manage response times for your agents.</li>
|
||||
<li>Add a Knowledge Base for self-serve support.</li>
|
||||
<li>Check out our documentation at <a href="https://docs.frappe.io/helpdesk" style="color: #3b82f6;">https://docs.frappe.io/helpdesk</a> </li>
|
||||
</ul>
|
||||
<br>
|
||||
Feel free to click around and explore our product.
|
||||
<br><br>
|
||||
If you need help, reach out to us here: <a href="https://support.frappe.io/helpdesk" style="color: #3b82f6;">https://support.frappe.io/helpdesk</a>
|
||||
<br><br>
|
||||
Cheers,<br>
|
||||
Team Frappe
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
@@ -31,9 +60,23 @@ def create_ticket():
|
||||
if frappe.db.count("HD Ticket"):
|
||||
return
|
||||
|
||||
# Render template with the current user's information
|
||||
user_doc = frappe.get_doc("User", frappe.session.user)
|
||||
if (user_doc.name or "").strip().lower() == "administrator":
|
||||
first_name = "there"
|
||||
else:
|
||||
first_name = (user_doc.first_name or "").strip() or "there"
|
||||
rendered_content = frappe.render_template(
|
||||
CONTENT,
|
||||
{
|
||||
"first_name": first_name,
|
||||
},
|
||||
safe_render=True,
|
||||
)
|
||||
|
||||
d = frappe.new_doc("HD Ticket")
|
||||
d.subject = "Welcome to Helpdesk"
|
||||
d.description = CONTENT
|
||||
d.description = rendered_content
|
||||
d.raised_by = AUTHOR_EMAIl
|
||||
d.contact = AUTHOR_NAME
|
||||
d.via_customer_portal = True
|
||||
|
||||
+1
-7
@@ -3,14 +3,8 @@
|
||||
"scripts": {
|
||||
"postinstall": "cd desk && yarn install",
|
||||
"dev": "cd desk && yarn dev",
|
||||
"build": "cd desk && yarn build",
|
||||
"disable-workspaces": "sed -i '' 's/\"workspaces\"/\"aworkspaces\"/g' package.json",
|
||||
"enable-workspaces": "sed -i '' 's/\"aworkspaces\"/\"workspaces\"/g' package.json && rm -rf node_modules ./desk/node_modules/ frappe-ui/node_modules/ && yarn install"
|
||||
"build": "cd desk && yarn build"
|
||||
},
|
||||
"workspaces": [
|
||||
"desk",
|
||||
"frappe-ui"
|
||||
],
|
||||
"resolutions": {
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"prosemirror-model": "1.25.4"
|
||||
|
||||
@@ -1788,9 +1788,9 @@
|
||||
integrity sha512-/c9VF1HBxj+AP54XGVgCmD9bEGYc5w5OofYCFQgM7l7PB1J00A4vOke0oPkHJnqnOOyPlFaxO/7N6l3XwFcnKA==
|
||||
|
||||
"@tiptap/extension-gapcursor@^3.20.5":
|
||||
version "3.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.5.tgz#0fe2ffb1d7669fc4f5541a0c66342da4107b08f8"
|
||||
integrity sha512-H+bRr+mqU/DQq1vfoMlppK1o+RbfSKYBMIcAMHWOez+C96MWfj5bhooVU2HLtl4XGmQxKGr3oEOCKDPdtRNThg==
|
||||
version "3.22.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.0.tgz#dbc50979d451c8f775b23550bc59b578b66a695c"
|
||||
integrity sha512-VsnaTU88PlA/eG9DtUvuB90z5gVZIaH6T/JVTxGasxR4CFsv0L4Zq5awwr0+SsYH9dKepRMgbanVU03c6k1SuA==
|
||||
|
||||
"@tiptap/extension-hard-break@^2.27.2":
|
||||
version "2.27.2"
|
||||
@@ -2632,14 +2632,6 @@ agent-base@6:
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
aggregate-error@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
|
||||
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
|
||||
dependencies:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
ajv@^8.6.0:
|
||||
version "8.18.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
|
||||
@@ -2737,11 +2729,6 @@ arraybuffer.prototype.slice@^1.0.4:
|
||||
get-intrinsic "^1.2.6"
|
||||
is-array-buffer "^3.0.4"
|
||||
|
||||
[email protected], assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
|
||||
|
||||
assertion-error@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
|
||||
@@ -3049,11 +3036,6 @@ classnames@^2.2.5:
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
|
||||
|
||||
cli-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||
@@ -3207,11 +3189,6 @@ core-js@^3.1.3:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.49.0.tgz#8b4d520ac034311fa21aa616f017ada0e0dbbddd"
|
||||
integrity sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==
|
||||
|
||||
[email protected]:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
||||
|
||||
crelt@^1.0.0, crelt@^1.0.5, crelt@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
|
||||
@@ -3794,16 +3771,6 @@ extend-shallow@^2.0.1:
|
||||
dependencies:
|
||||
is-extendable "^0.1.0"
|
||||
|
||||
[email protected]:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
|
||||
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
|
||||
|
||||
fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
@@ -3910,10 +3877,10 @@ fraction.js@^5.3.4:
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a"
|
||||
integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==
|
||||
|
||||
[email protected]69:
|
||||
version "0.1.269"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.269.tgz#d694cd23fe6eadbd7bb898bb3f46e0414c27f829"
|
||||
integrity sha512-1D4AW8cPoC7OIRqEzYnF169/r+v0EfVBvfCi/ovmk76FBPKY+qIVOzL+Vx/9TcYvJubc1tSU19F/jX177e3axA==
|
||||
[email protected]71:
|
||||
version "0.1.271"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.271.tgz#206aa52f4faa541da0cfdb2b32ae845e622b08e1"
|
||||
integrity sha512-H6FDKLynUCXxo3eIainJd6FjWrBtAZ3WZXVRM4tvLT3J7x7P5JjoNefB6YD8WZ+MQThKSwMC+/Kzg8vY2mc5XQ==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.7.4"
|
||||
"@floating-ui/vue" "^1.1.6"
|
||||
@@ -4338,11 +4305,6 @@ ignore@^5.2.4:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
indent-string@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
@@ -4717,7 +4679,7 @@ json-schema-traverse@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
|
||||
[email protected], json-schema@^0.4.0:
|
||||
json-schema@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
|
||||
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
|
||||
@@ -6051,6 +6013,11 @@ set-proto@^1.0.0:
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
shell-quote@^1.8.3:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b"
|
||||
integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
|
||||
|
||||
shiki-es@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shiki-es/-/shiki-es-0.2.0.tgz#ae5bced62dca0ba46ee81149e68d428565a3e6fb"
|
||||
@@ -6883,15 +6850,6 @@ [email protected]:
|
||||
"@vue/devtools-api" "^7.5.2"
|
||||
type-fest "^4.8.3"
|
||||
|
||||
[email protected]:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
[email protected]:
|
||||
version "2.1.9"
|
||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f"
|
||||
|
||||
Reference in New Issue
Block a user