522 lines
12 KiB
Vue
Executable File
522 lines
12 KiB
Vue
Executable File
<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>
|