conflict-nuxt-4/app/components/auto-import/BlockMenu.vue
2026-02-12 11:24:27 +03:30

522 lines
12 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="visible" class="block-menu" :style="menuStyle" @click.stop>
<!-- هدر بلوک -->
<div class="block-header">
<div class="block-type">
<span class="block-icon">{{ blockIcon }}</span>
<span class="block-name">{{ blockTypeName }}</span>
</div>
<button class="close-btn" @click="closeMenu">×</button>
</div>
<!-- تنظیمات بلوک -->
<div class="menu-section">
<div class="section-title">تنظیمات بلوک</div>
<!-- رنگ بلوک -->
<div class="block-color-picker">
<div class="color-label">رنگ:</div>
<div class="color-grid">
<button
v-for="color in blockColors"
:key="color.value"
class="color-option"
:class="{ selected: blockColor === color.value }"
:style="{ backgroundColor: color.value }"
@click="handleAction('color', color.value)"
:title="color.name"
></button>
</div>
</div>
<!-- ترازبندی -->
<div class="block-alignment">
<div class="alignment-label">تراز:</div>
<div class="alignment-buttons">
<button
v-for="align in alignments"
:key="align.value"
class="align-btn"
:class="{ selected: blockAlign === align.value }"
@click="handleAction('align', align.value)"
:title="align.name"
>
{{ align.icon }}
</button>
</div>
</div>
</div>
<!-- تبدیل نوع بلوک -->
<div class="menu-section">
<div class="section-title">تبدیل به</div>
<div class="block-types-grid">
<button
v-for="type in availableBlockTypes"
:key="type.id"
class="type-btn"
:class="{ current: blockType === type.id }"
@click="handleAction('convert', type.id)"
>
<span class="type-icon">{{ type.icon }}</span>
<span class="type-name">{{ type.name }}</span>
</button>
</div>
</div>
<!-- عملیات بلوک -->
<div class="menu-section">
<div class="section-title">عملیات</div>
<button class="menu-item" @click="handleAction('duplicate')">
<span class="item-icon">📋</span>
تکثیر بلوک
<span class="hint">ایجاد کپی</span>
</button>
<button class="menu-item" @click="handleAction('comment')">
<span class="item-icon">💬</span>
افزودن نظر
<span class="hint">کامنت</span>
</button>
<button class="menu-item" @click="handleAction('move-up')">
<span class="item-icon">⬆️</span>
انتقال به بالا
</button>
<button class="menu-item" @click="handleAction('move-down')">
<span class="item-icon">⬇️</span>
انتقال به پایین
</button>
<button
class="menu-item text-danger"
@click="handleAction('delete-block')"
>
<span class="item-icon">🗑️</span>
حذف بلوک
<span class="hint danger">برای همیشه</span>
</button>
</div>
<!-- AI مخصوص بلوک -->
<div class="menu-section">
<div class="section-title">AI برای این بلوک</div>
<button class="menu-item ai-item" @click="handleAction('ai-rewrite')">
<span class="item-icon">✍️</span>
بازنویسی هوشمند
<span class="ai-badge">AI</span>
</button>
<button class="menu-item ai-item" @click="handleAction('ai-expand')">
<span class="item-icon">🔍</span>
گسترش محتوا
<span class="ai-badge">AI</span>
</button>
<button class="menu-item ai-item" @click="handleAction('ai-simplify')">
<span class="item-icon">📖</span>
ساده‌سازی
<span class="ai-badge">AI</span>
</button>
</div>
<!-- تنظیمات پیشرفته -->
<div class="menu-section">
<button class="menu-item" @click="handleAction('settings')">
<span class="item-icon">⚙️</span>
تنظیمات پیشرفته
</button>
<button class="menu-item" @click="handleAction('export')">
<span class="item-icon">📤</span>
خروجی گرفتن
</button>
<button class="menu-item" @click="handleAction('history')">
<span class="item-icon">🕒</span>
مشاهده تاریخچه
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const props = defineProps({
visible: Boolean,
position: {
type: Object,
default: () => ({ x: 0, y: 0 }),
},
blockType: {
type: String,
default: "paragraph",
},
blockData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["close", "action"]);
const menuStyle = computed(() => ({
top: `${props.position.y}px`,
left: `${props.position.x}px`,
display: props.visible ? "block" : "none",
}));
// داده‌های بلوک
const blockColor = ref(props.blockData.color || "#ffffff");
const blockAlign = ref(props.blockData.align || "right");
// آیکون و نام بلوک
const blockIcon = computed(() => {
const icons = {
paragraph: "📝",
heading1: "H1",
heading2: "H2",
heading3: "H3",
todo: "✓",
bullet: "•",
number: "1.",
code: "{ }",
quote: '"',
image: "🖼️",
file: "📎",
table: "📊",
callout: "💡",
};
return icons[props.blockType] || "📝";
});
const blockTypeName = computed(() => {
const names = {
paragraph: "متن",
heading1: "عنوان ۱",
heading2: "عنوان ۲",
heading3: "عنوان ۳",
todo: "لیست کار",
bullet: "لیست نقطه‌ای",
number: "لیست شماره‌ای",
code: "کد",
quote: "نقل قول",
image: "تصویر",
file: "فایل",
table: "جدول",
callout: "کالاوت",
};
return names[props.blockType] || "بلوک";
});
// رنگ‌های بلوک
const blockColors = [
{ name: "پیش‌فرض", value: "#ffffff" },
{ name: "آبی روشن", value: "#dbeafe" },
{ name: "سبز روشن", value: "#dcfce7" },
{ name: "زرد روشن", value: "#fef3c7" },
{ name: "صورتی روشن", value: "#fce7f3" },
{ name: "بنفش روشن", value: "#f3e8ff" },
{ name: "خاکستری", value: "#f3f4f6" },
];
// ترازبندی
const alignments = [
{ name: "راست", value: "right", icon: "←" },
{ name: "وسط", value: "center", icon: "↔" },
{ name: "چپ", value: "left", icon: "→" },
];
// انواع بلوک‌های قابل تبدیل
const availableBlockTypes = [
{ id: "paragraph", name: "متن", icon: "📝" },
{ id: "heading1", name: "عنوان ۱", icon: "H1" },
{ id: "heading2", name: "عنوان ۲", icon: "H2" },
{ id: "heading3", name: "عنوان ۳", icon: "H3" },
{ id: "todo", name: "لیست کار", icon: "✓" },
{ id: "bullet", name: "لیست نقطه‌ای", icon: "•" },
{ id: "number", name: "لیست شماره‌ای", icon: "1." },
{ id: "code", name: "کد", icon: "{ }" },
{ id: "quote", name: "نقل قول", icon: '"' },
{ id: "callout", name: "کالاوت", icon: "💡" },
];
const handleAction = (action, value = null) => {
emit("action", {
action,
value,
blockType: props.blockType,
blockData: props.blockData,
});
};
const closeMenu = () => {
emit("close");
};
// بستن منو با کلیک خارج
const handleClickOutside = (event) => {
if (!event.target.closest(".block-menu")) {
closeMenu();
}
};
onMounted(() => {
document.addEventListener("click", handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener("click", handleClickOutside);
});
</script>
<style scoped>
.block-menu {
position: fixed;
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
width: 320px;
max-height: 600px;
overflow-y: auto;
z-index: 10001;
animation: slideIn 0.2s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* هدر بلوک */
.block-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f3f4f6;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 12px 12px 0 0;
}
.block-type {
display: flex;
align-items: center;
gap: 12px;
}
.block-icon {
font-size: 24px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.block-name {
font-weight: 600;
color: #1f2937;
font-size: 16px;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #6b7280;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
}
.close-btn:hover {
background: #f3f4f6;
color: #374151;
}
/* تنظیمات بلوک */
.block-color-picker,
.block-alignment {
padding: 12px 16px;
}
.color-label,
.alignment-label {
font-size: 14px;
color: #6b7280;
margin-bottom: 8px;
display: block;
}
.color-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
}
.color-option {
width: 28px;
height: 28px;
border-radius: 6px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
}
.color-option:hover {
transform: scale(1.1);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.color-option.selected {
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.alignment-buttons {
display: flex;
gap: 8px;
}
.align-btn {
flex: 1;
padding: 8px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 6px;
cursor: pointer;
font-size: 18px;
transition: all 0.2s;
}
.align-btn:hover {
background: #e5e7eb;
}
.align-btn.selected {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
/* تبدیل نوع بلوک */
.block-types-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding: 0 16px;
}
.type-btn {
padding: 12px 8px;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.type-btn:hover {
background: #f3f4f6;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.type-btn.current {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
.type-icon {
font-size: 20px;
}
.type-name {
font-size: 12px;
}
/* آیتم‌های منو */
.menu-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 16px;
border: none;
background: none;
text-align: right;
cursor: pointer;
transition: all 0.2s;
color: #374151;
font-size: 14px;
position: relative;
}
.menu-item:hover {
background-color: #f9fafb;
}
.item-icon {
font-size: 18px;
width: 24px;
text-align: center;
}
.hint {
font-size: 12px;
color: #9ca3af;
margin-right: auto;
}
.hint.danger {
color: #ef4444;
}
.ai-badge {
font-size: 10px;
padding: 2px 6px;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: white;
border-radius: 12px;
margin-right: auto;
}
.ai-item {
background: linear-gradient(135deg, #f5f3ff 0%, #fdf2f8 100%);
}
.ai-item:hover {
background: linear-gradient(135deg, #ede9fe 0%, #fce7f3 100%);
}
.menu-item.text-danger {
color: #ef4444;
}
.menu-item.text-danger:hover {
background-color: #fef2f2;
}
/* ریسپانسیو */
@media (max-width: 768px) {
.block-menu {
width: 280px;
}
.block-types-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>