hesabixSite/templates/qa/ask_question.html.twig
Hesabix 3c7fa1b8a4
Some checks failed
PHP Composer / build (push) Has been cancelled
progress in qa part
2025-09-05 20:33:33 +03:30

1150 lines
54 KiB
Twig

{% extends 'base.html.twig' %}
{% block title %}پرسیدن سوال جدید - پرسش و پاسخ{% endblock %}
{% block body %}
<main class="min-h-screen bg-gray-50">
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<!-- هدر فرم -->
<div class="text-center mb-8">
<h1 class="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
پرسیدن سوال جدید
</h1>
<p class="text-lg text-gray-600">
سوال خود را مطرح کنید تا از تجربه دیگران استفاده کنید
</p>
</div>
<!-- فرم ایجاد سوال -->
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
<div class="p-8">
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-8', 'data-turbo': 'false', 'enctype': 'multipart/form-data'}}) }}
<!-- عنوان سوال -->
<div>
<label for="{{ form.title.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
عنوان سوال
</label>
{{ form_widget(form.title, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200'}}) }}
{{ form_errors(form.title) }}
<p class="mt-2 text-sm text-gray-600">
عنوان سوال باید واضح و مختصر باشد. سعی کنید مشکل خود را در یک جمله خلاصه کنید.
</p>
</div>
<!-- محتوای سوال -->
<div>
<label for="{{ form.content.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
محتوای سوال
</label>
<!-- نوار ابزار ادیتور -->
<div class="flex flex-wrap gap-2 p-3 bg-gray-50 border border-gray-300 rounded-t-xl">
<button type="button"
onclick="formatText('bold')"
title="پررنگ"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
</svg>
</button>
<button type="button"
onclick="formatText('italic')"
title="کج"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="19" y1="4" x2="10" y2="4"></line>
<line x1="14" y1="20" x2="5" y2="20"></line>
<line x1="15" y1="4" x2="9" y2="20"></line>
</svg>
</button>
<button type="button"
onclick="formatText('code')"
title="کد"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="16,18 22,12 16,6"></polyline>
<polyline points="8,6 2,12 8,18"></polyline>
</svg>
</button>
<button type="button"
onclick="insertList()"
title="لیست"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<line x1="3" y1="6" x2="3.01" y2="6"></line>
<line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>
</button>
</div>
{{ form_widget(form.content, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 border-t-0 rounded-b-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200', 'rows': 10}}) }}
{{ form_errors(form.content) }}
<p class="mt-2 text-sm text-gray-600">
سوال خود را به تفصیل شرح دهید. هرچه جزئیات بیشتری ارائه دهید، پاسخ‌های بهتری دریافت خواهید کرد.
<br><span class="text-gray-500">می‌توانید از دکمه‌های بالا برای فرمت کردن متن استفاده کنید.</span>
</p>
</div>
<!-- تگ‌ها -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
تگ‌ها
<span class="text-red-500">*</span>
</label>
<!-- فیلد مخفی Symfony برای تگ‌ها -->
{{ form_widget(form.tags, {'attr': {'class': 'hidden'}}) }}
{{ form_errors(form.tags) }}
<div class="border border-gray-300 rounded-xl p-6 bg-gradient-to-br from-gray-50 to-blue-50">
<!-- تگ‌های انتخاب شده -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-semibold text-gray-700">تگ‌های انتخاب شده</h4>
<span class="text-xs text-gray-500" id="tag-count">0 تگ انتخاب شده</span>
</div>
<div class="selected-tags min-h-12 flex flex-wrap gap-2 p-3 bg-white rounded-lg border-2 border-dashed border-gray-200" id="selected-tags">
<div class="flex items-center text-gray-400 text-sm">
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
هیچ تگی انتخاب نشده است
</div>
</div>
</div>
<!-- جستجو و افزودن تگ -->
<div class="mb-6">
<div class="relative">
<input type="text"
class="w-full px-4 py-3 pr-12 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-sm"
id="tag-input"
placeholder="نام تگ را تایپ کنید...">
<button class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors duration-200"
type="button"
id="search-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</div>
<div class="flex items-center justify-between mt-3">
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200 text-sm font-medium flex items-center space-x-2 space-x-reverse"
type="button"
id="add-tag-btn">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span>افزودن تگ</span>
</button>
<span class="text-xs text-gray-500">حداقل 1 و حداکثر 5 تگ</span>
</div>
</div>
<!-- تگ‌های پیشنهادی -->
<div>
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-semibold text-gray-700">تگ‌های محبوب</h4>
<button class="text-xs text-blue-600 hover:text-blue-800 transition-colors duration-200"
type="button"
id="show-all-tags">
نمایش همه
</button>
</div>
<div class="flex flex-wrap gap-2" id="tag-suggestions">
{% for tag in availableTags %}
<button type="button"
class="inline-flex items-center px-3 py-2 bg-white text-gray-700 text-sm font-medium rounded-full border border-gray-300 hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700 cursor-pointer transition-all duration-200 tag-suggestion"
data-tag-id="{{ tag.id }}"
data-tag-name="{{ tag.name }}">
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
{{ tag.name }}
</button>
{% endfor %}
</div>
</div>
</div>
<p class="mt-2 text-sm text-gray-600">
تگ‌های مرتبط را انتخاب کنید تا دیگران راحت‌تر بتوانند سوال شما را پیدا کنند.
<br><span class="text-gray-500">می‌توانید تگ جدید ایجاد کنید یا از تگ‌های موجود انتخاب کنید.</span>
</p>
</div>
<!-- اطلاع‌رسانی ایمیل -->
<div>
<div class="flex items-center space-x-3 space-x-reverse">
{{ form_widget(form.notifyOnAnswer, {'attr': {'class': 'form-check-input'}}) }}
<label for="{{ form.notifyOnAnswer.vars.id }}" class="text-sm font-medium text-gray-700">
اطلاع‌رسانی ایمیل هنگام دریافت پاسخ
</label>
</div>
<p class="mt-2 text-sm text-gray-600">
در صورت انتخاب این گزینه، هر زمان که برای سوال شما پاسخی ارسال شود، از طریق ایمیل مطلع خواهید شد.
</p>
</div>
<!-- پیوست فایل -->
<div>
<label for="{{ form.attachments.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
پیوست فایل
<span class="text-gray-500">(اختیاری)</span>
</label>
<div class="border-2 border-dashed border-gray-300 rounded-xl p-6 bg-gray-50 hover:bg-gray-100 transition-colors duration-200">
{{ form_widget(form.attachments, {'attr': {'class': 'w-full', 'id': 'file-input'}}) }}
{{ form_errors(form.attachments) }}
<div class="mt-4 text-center">
<div class="flex items-center justify-center space-x-2 space-x-reverse text-gray-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
</svg>
<span class="text-sm">فایل‌های خود را اینجا بکشید یا کلیک کنید</span>
</div>
<p class="text-xs text-gray-400 mt-2">
فرمت‌های مجاز: JPG, PNG, GIF, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT
<br>حداکثر 3 فایل، هر کدام 4 مگابایت
</p>
</div>
<!-- نمایش فایل‌های انتخاب شده -->
<div id="selected-files" class="mt-4 hidden">
<h4 class="text-sm font-medium text-gray-700 mb-2">فایل‌های انتخاب شده:</h4>
<div id="file-list" class="space-y-2"></div>
</div>
</div>
</div>
<!-- دکمه‌های فرم -->
<div class="flex justify-between items-center pt-6 border-t border-gray-200">
<a href="{{ path('qa_index') }}"
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 12H5"></path>
<polyline points="12,19 5,12 12,5"></polyline>
</svg>
<span>انصراف</span>
</a>
<button type="submit"
class="inline-flex items-center space-x-2 space-x-reverse px-8 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9 22,2"></polygon>
</svg>
<span>ارسال سوال</span>
</button>
</div>
{{ form_end(form) }}
</div>
</div>
<!-- راهنمای پرسیدن سوال -->
<div class="bg-white rounded-2xl shadow-soft mt-8 overflow-hidden">
<div class="bg-gradient-to-r from-blue-50 to-purple-50 px-8 py-6">
<h3 class="text-xl font-semibold text-gray-900 flex items-center">
<svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4"></path>
<path d="M21 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M3 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M12 3c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M12 21c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
</svg>
راهنمای پرسیدن سوال خوب
</h3>
</div>
<div class="p-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h4 class="text-lg font-semibold text-green-700 mb-4 flex items-center">
<svg class="w-5 h-5 text-green-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
کارهای درست
</h4>
<ul class="space-y-3">
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">عنوان واضح و مختصر</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">شرح کامل مشکل</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">انتخاب تگ‌های مناسب</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">استفاده از کلمات کلیدی</span>
</li>
</ul>
</div>
<div>
<h4 class="text-lg font-semibold text-red-700 mb-4 flex items-center">
<svg class="w-5 h-5 text-red-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
کارهای نادرست
</h4>
<ul class="space-y-3">
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">عنوان مبهم</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">شرح ناکافی</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">عدم انتخاب تگ</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">سوال تکراری</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script>
// سیستم مدیریت تگ‌ها
if (typeof window.QuestionFormClasses === 'undefined') {
window.QuestionFormClasses = {};
}
if (typeof window.QuestionFormClasses.TagManager === 'undefined') {
window.QuestionFormClasses.TagManager = class TagManager {
constructor() {
this.selectedTags = [];
this.maxTags = 5;
this.minTags = 1;
this.init();
}
init() {
this.tagInput = document.getElementById('tag-input');
this.addTagBtn = document.getElementById('add-tag-btn');
this.selectedTagsContainer = document.getElementById('selected-tags');
this.tagSuggestions = document.querySelectorAll('.tag-suggestion');
this.tagCount = document.getElementById('tag-count');
this.showAllTagsBtn = document.getElementById('show-all-tags');
if (!this.tagInput || !this.addTagBtn || !this.selectedTagsContainer) {
console.error('Required elements not found for tag system');
return;
}
this.bindEvents();
this.loadExistingTags();
this.updateTagCount();
this.renderSelectedTags();
}
loadExistingTags() {
// بارگذاری تگ‌های موجود از فرم Symfony
const form = document.querySelector('form');
if (!form) return;
const tagsField = form.querySelector('select[name="question_form[tags][]"]') ||
form.querySelector('select[id*="tags"]');
if (!tagsField) return;
// اطمینان از اینکه فیلد multiple است
tagsField.multiple = true;
const selectedOptions = tagsField.querySelectorAll('option:checked');
selectedOptions.forEach(option => {
const tagId = option.value;
const tagName = option.textContent;
// بررسی اینکه تگ قبلاً اضافه نشده باشد
if (!this.selectedTags.find(tag => tag.id === tagId)) {
this.selectedTags.push({ id: tagId, name: tagName });
}
});
console.log('Loaded existing tags:', this.selectedTags);
}
bindEvents() {
// حذف event listener های قبلی
this.removeEventListeners();
// افزودن تگ با دکمه
this.addTagBtn.addEventListener('click', this.handleAddTagClick.bind(this));
// افزودن تگ با Enter
this.tagInput.addEventListener('keypress', this.handleKeyPress.bind(this));
// جستجوی تگ‌ها
this.tagInput.addEventListener('input', this.handleInputChange.bind(this));
// کلیک روی تگ‌های پیشنهادی
this.tagSuggestions.forEach(suggestion => {
suggestion.addEventListener('click', () => this.toggleTagSuggestion(suggestion));
});
// نمایش همه تگ‌ها
if (this.showAllTagsBtn) {
this.showAllTagsBtn.addEventListener('click', this.handleShowAllTags.bind(this));
}
}
removeEventListeners() {
if (this.addTagBtn) {
this.addTagBtn.removeEventListener('click', this.handleAddTagClick);
}
if (this.tagInput) {
this.tagInput.removeEventListener('keypress', this.handleKeyPress);
this.tagInput.removeEventListener('input', this.handleInputChange);
}
if (this.showAllTagsBtn) {
this.showAllTagsBtn.removeEventListener('click', this.handleShowAllTags);
}
}
handleAddTagClick() {
this.addTagFromInput();
}
handleKeyPress(e) {
if (e.key === 'Enter') {
e.preventDefault();
this.addTagFromInput();
}
}
handleInputChange() {
this.filterSuggestions();
}
handleShowAllTags() {
this.showAllTags();
}
addTag(tagId, tagName) {
// بررسی محدودیت تعداد تگ‌ها
if (this.selectedTags.length >= this.maxTags) {
if (window.notification) {
window.notification.warning(`حداکثر ${this.maxTags} تگ می‌توانید انتخاب کنید`);
}
return false;
}
// بررسی وجود تگ
if (this.selectedTags.find(tag => tag.id === tagId)) {
if (window.notification) {
window.notification.info('این تگ قبلاً انتخاب شده است');
}
return false;
}
this.selectedTags.push({ id: tagId, name: tagName });
this.updateTagCount();
this.renderSelectedTags();
this.updateHiddenInput();
this.updateSuggestionState(tagId, true);
// نمایش پیام موفقیت فقط یک بار
if (window.notification && !this._showingMessage) {
this._showingMessage = true;
window.notification.success(`تگ "${tagName}" اضافه شد`);
setTimeout(() => {
this._showingMessage = false;
}, 1000);
}
return true;
}
removeTag(tagId) {
const tagIndex = this.selectedTags.findIndex(tag => tag.id === tagId);
if (tagIndex === -1) return false;
const removedTag = this.selectedTags[tagIndex];
this.selectedTags.splice(tagIndex, 1);
this.updateTagCount();
this.renderSelectedTags();
this.updateHiddenInput();
this.updateSuggestionState(tagId, false);
if (window.notification) {
window.notification.info(`تگ "${removedTag.name}" حذف شد`);
}
return true;
}
addTagFromInput() {
const tagName = this.tagInput.value.trim();
if (!tagName) {
if (window.notification) {
window.notification.warning('لطفاً نام تگ را وارد کنید');
}
return;
}
// بررسی تگ موجود
const existingTag = Array.from(this.tagSuggestions).find(suggestion =>
suggestion.dataset.tagName.toLowerCase() === tagName.toLowerCase()
);
if (existingTag) {
const tagId = existingTag.dataset.tagId;
const tagName = existingTag.dataset.tagName;
this.addTag(tagId, tagName);
this.tagInput.value = '';
return;
}
// ایجاد تگ جدید
this.createNewTag(tagName);
}
createNewTag(tagName) {
// نمایش حالت بارگذاری
const originalContent = this.addTagBtn.innerHTML;
this.addTagBtn.innerHTML = '<div class="loading-spinner"></div>';
this.addTagBtn.disabled = true;
fetch('/qa/tag/create', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: `name=${encodeURIComponent(tagName)}&_token={{ csrf_token('question') }}`
})
.then(response => response.json())
.then(data => {
if (data.error) {
if (window.notification) {
window.notification.error(data.error);
}
return;
}
// افزودن تگ جدید
if (this.addTag(data.id, data.name)) {
this.tagInput.value = '';
// افزودن به لیست پیشنهادی
this.addToSuggestions(data.id, data.name);
}
})
.catch(error => {
console.error('Error creating tag:', error);
if (window.notification) {
window.notification.error('خطا در ایجاد تگ جدید. لطفاً دوباره تلاش کنید.');
}
})
.finally(() => {
// بازگردانی دکمه
this.addTagBtn.innerHTML = originalContent;
this.addTagBtn.disabled = false;
});
}
addToSuggestions(tagId, tagName) {
const suggestionElement = document.createElement('button');
suggestionElement.type = 'button';
suggestionElement.className = 'inline-flex items-center px-3 py-2 bg-white text-gray-700 text-sm font-medium rounded-full border border-gray-300 hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700 cursor-pointer transition-all duration-200 tag-suggestion';
suggestionElement.dataset.tagId = tagId;
suggestionElement.dataset.tagName = tagName;
suggestionElement.innerHTML = `
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
${tagName}
`;
suggestionElement.addEventListener('click', () => this.toggleTagSuggestion(suggestionElement));
const container = document.getElementById('tag-suggestions');
if (container) {
container.appendChild(suggestionElement);
this.tagSuggestions = document.querySelectorAll('.tag-suggestion');
}
}
toggleTagSuggestion(suggestion) {
const tagId = suggestion.dataset.tagId;
const tagName = suggestion.dataset.tagName;
if (this.selectedTags.find(tag => tag.id === tagId)) {
this.removeTag(tagId);
} else {
this.addTag(tagId, tagName);
}
}
updateSuggestionState(tagId, isSelected) {
const suggestion = document.querySelector(`[data-tag-id="${tagId}"]`);
if (!suggestion) return;
if (isSelected) {
suggestion.classList.remove('bg-white', 'text-gray-700', 'border-gray-300');
suggestion.classList.add('bg-blue-600', 'text-white', 'border-blue-600');
} else {
suggestion.classList.remove('bg-blue-600', 'text-white', 'border-blue-600');
suggestion.classList.add('bg-white', 'text-gray-700', 'border-gray-300');
}
}
renderSelectedTags() {
this.selectedTagsContainer.innerHTML = '';
if (this.selectedTags.length === 0) {
this.selectedTagsContainer.innerHTML = `
<div class="flex items-center text-gray-400 text-sm">
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
هیچ تگی انتخاب نشده است
</div>
`;
return;
}
this.selectedTags.forEach(tag => {
const tagElement = document.createElement('div');
tagElement.className = 'inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-full shadow-sm';
tagElement.innerHTML = `
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
${tag.name}
<button type="button"
class="tag-remove mr-2 text-white hover:text-red-200 transition-colors duration-200"
data-tag-id="${tag.id}">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`;
// رویداد حذف تگ
const removeBtn = tagElement.querySelector('.tag-remove');
removeBtn.addEventListener('click', () => this.removeTag(tag.id));
this.selectedTagsContainer.appendChild(tagElement);
});
}
updateTagCount() {
if (this.tagCount) {
this.tagCount.textContent = `${this.selectedTags.length} تگ انتخاب شده`;
}
}
updateHiddenInput() {
const form = document.querySelector('form');
if (!form) {
console.error('Form not found for updating hidden inputs');
return;
}
// پیدا کردن فیلد tags در فرم Symfony
const tagsField = form.querySelector('select[name="question_form[tags][]"]') ||
form.querySelector('select[id*="tags"]');
if (!tagsField) {
console.error('Tags field not found in form');
return;
}
// اطمینان از اینکه فیلد multiple است
tagsField.multiple = true;
// پاک کردن گزینه‌های قبلی
tagsField.innerHTML = '';
// افزودن گزینه‌های جدید
this.selectedTags.forEach(tag => {
const option = document.createElement('option');
option.value = tag.id;
option.textContent = tag.name;
option.selected = true;
tagsField.appendChild(option);
});
console.log('Updated tags field with', this.selectedTags.length, 'tags');
}
filterSuggestions() {
const searchTerm = this.tagInput.value.toLowerCase();
this.tagSuggestions.forEach(suggestion => {
const tagName = suggestion.dataset.tagName.toLowerCase();
if (tagName.includes(searchTerm)) {
suggestion.style.display = 'inline-flex';
} else {
suggestion.style.display = 'none';
}
});
}
showAllTags() {
this.tagSuggestions.forEach(suggestion => {
suggestion.style.display = 'inline-flex';
});
this.tagInput.value = '';
}
validateTags() {
if (this.selectedTags.length < this.minTags) {
// نمایش پیام خطای زیبا
this.showValidationError(`حداقل ${this.minTags} تگ باید انتخاب کنید`);
return false;
}
return true;
}
showValidationError(message) {
// حذف پیام خطای قبلی اگر وجود دارد
const existingError = document.querySelector('.tag-validation-error');
if (existingError) {
existingError.remove();
}
// ایجاد پیام خطای زیبا
const errorDiv = document.createElement('div');
errorDiv.className = 'tag-validation-error mt-4 p-4 bg-red-50 border border-red-200 rounded-lg';
errorDiv.innerHTML = `
<div class="flex items-center">
<svg class="w-5 h-5 text-red-500 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span class="text-red-700 font-medium">${message}</span>
</div>
`;
// اضافه کردن پیام خطا به فرم
const form = document.querySelector('form');
if (form) {
const submitButton = form.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.parentNode.insertBefore(errorDiv, submitButton);
}
}
// حذف خودکار پیام خطا بعد از 5 ثانیه
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.remove();
}
}, 5000);
}
}
}
// ادیتور متن
if (typeof window.QuestionFormClasses.TextEditor === 'undefined') {
window.QuestionFormClasses.TextEditor = class TextEditor {
constructor() {
this.textarea = document.querySelector('textarea[name="question[content]"]');
}
formatText(command) {
if (!this.textarea) return;
const start = this.textarea.selectionStart;
const end = this.textarea.selectionEnd;
const selectedText = this.textarea.value.substring(start, end);
let formattedText = '';
switch(command) {
case 'bold':
formattedText = `**${selectedText}**`;
break;
case 'italic':
formattedText = `*${selectedText}*`;
break;
case 'code':
formattedText = `\`${selectedText}\``;
break;
}
this.textarea.value = this.textarea.value.substring(0, start) + formattedText + this.textarea.value.substring(end);
this.textarea.focus();
this.textarea.setSelectionRange(start + formattedText.length, start + formattedText.length);
}
insertList() {
if (!this.textarea) return;
const start = this.textarea.selectionStart;
const end = this.textarea.selectionEnd;
const selectedText = this.textarea.value.substring(start, end);
const listText = selectedText.split('\n').map(line => `- ${line}`).join('\n');
this.textarea.value = this.textarea.value.substring(0, start) + listText + this.textarea.value.substring(end);
this.textarea.focus();
}
}
}
// مدیریت فایل‌ها
if (typeof window.QuestionFormClasses.FileManager === 'undefined') {
window.QuestionFormClasses.FileManager = class FileManager {
constructor() {
this.maxFiles = 3;
this.maxFileSize = 4 * 1024 * 1024; // 4MB
this.allowedTypes = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'text/plain'
];
this.selectedFiles = [];
this.init();
}
init() {
// پیدا کردن input file - ممکن است multiple باشد
this.fileInput = document.querySelector('input[type="file"][name*="attachments"]');
this.selectedFilesDiv = document.getElementById('selected-files');
this.fileListDiv = document.getElementById('file-list');
if (!this.fileInput) {
console.error('File input not found');
return;
}
this.bindEvents();
}
bindEvents() {
if (!this.fileInput) return;
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
// Drag and drop
const dropZone = this.fileInput.closest('.border-dashed');
if (dropZone) {
dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
dropZone.addEventListener('drop', (e) => this.handleDrop(e));
dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
}
}
handleFileSelect(event) {
const files = Array.from(event.target.files);
this.processFiles(files);
}
handleDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add('bg-blue-50', 'border-blue-400');
}
handleDragLeave(event) {
event.preventDefault();
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
}
handleDrop(event) {
event.preventDefault();
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
const files = Array.from(event.dataTransfer.files);
this.processFiles(files);
}
processFiles(files) {
files.forEach(file => {
if (this.validateFile(file)) {
this.addFile(file);
}
});
this.updateFileInput();
// اطمینان از اینکه فایل‌ها در input قرار گرفته‌اند
setTimeout(() => {
this.updateFileInput();
}, 100);
}
validateFile(file) {
// بررسی تعداد فایل‌ها
if (this.selectedFiles.length >= this.maxFiles) {
if (window.notification) {
window.notification.warning(`حداکثر ${this.maxFiles} فایل می‌توانید انتخاب کنید`);
}
return false;
}
// بررسی نوع فایل
if (!this.allowedTypes.includes(file.type)) {
if (window.notification) {
window.notification.error('فرمت فایل مجاز نیست');
}
return false;
}
// بررسی حجم فایل
if (file.size > this.maxFileSize) {
if (window.notification) {
window.notification.error('حجم فایل نمی‌تواند بیش از 4 مگابایت باشد');
}
return false;
}
return true;
}
addFile(file) {
const fileId = Date.now() + Math.random();
this.selectedFiles.push({
id: fileId,
file: file,
name: file.name,
size: file.size,
type: file.type
});
this.renderFiles();
// اطمینان از اینکه فایل به input اضافه شده است
this.updateFileInput();
}
removeFile(fileId) {
this.selectedFiles = this.selectedFiles.filter(f => f.id !== fileId);
this.renderFiles();
this.updateFileInput();
}
renderFiles() {
if (this.selectedFiles.length === 0) {
this.selectedFilesDiv.classList.add('hidden');
return;
}
this.selectedFilesDiv.classList.remove('hidden');
this.fileListDiv.innerHTML = '';
this.selectedFiles.forEach(fileData => {
const fileElement = document.createElement('div');
fileElement.className = 'flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200';
fileElement.innerHTML = `
<div class="flex items-center space-x-3 space-x-reverse">
<div class="flex-shrink-0">
${this.getFileIcon(fileData.type)}
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900 truncate">${fileData.name}</p>
<p class="text-xs text-gray-500">${this.formatFileSize(fileData.size)}</p>
</div>
</div>
<button type="button"
class="flex-shrink-0 text-red-600 hover:text-red-800 transition-colors duration-200"
onclick="fileManager.removeFile(${fileData.id})">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`;
this.fileListDiv.appendChild(fileElement);
});
}
getFileIcon(type) {
if (type.startsWith('image/')) {
return '<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21,15 16,10 5,21"></polyline></svg>';
} else if (type === 'application/pdf') {
return '<svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
} else if (type.includes('word') || type.includes('document')) {
return '<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
} else {
return '<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
}
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
updateFileInput() {
// بررسی وجود fileInput
if (!this.fileInput) {
console.error('File input is null, cannot update files');
return;
}
// تنظیم فایل‌ها در input اصلی
try {
const dt = new DataTransfer();
this.selectedFiles.forEach(fileData => {
dt.items.add(fileData.file);
});
this.fileInput.files = dt.files;
console.log('Updated file input with', this.selectedFiles.length, 'files');
console.log('File input files:', this.fileInput.files.length);
} catch (e) {
console.warn('DataTransfer not supported:', e);
// در صورت عدم پشتیبانی، فایل‌ها را در متغیر سراسری ذخیره می‌کنیم
window.selectedFiles = this.selectedFiles;
}
}
}
}
// متغیرهای سراسری - استفاده از window object
if (!window.questionFormManager) {
window.questionFormManager = {
tagManager: null,
textEditor: null,
fileManager: null,
initialized: false
};
}
// توابع سراسری برای HTML
window.formatText = function(command) {
if (window.questionFormManager.textEditor) {
window.questionFormManager.textEditor.formatText(command);
}
};
window.insertList = function() {
if (window.questionFormManager.textEditor) {
window.questionFormManager.textEditor.insertList();
}
};
// مقداردهی اولیه
function initializeQuestionForm() {
if (typeof window.notification === 'undefined') {
setTimeout(initializeQuestionForm, 100);
return;
}
// جلوگیری از مقداردهی مجدد
if (window.questionFormManager.initialized) {
return;
}
// ایجاد instance های جدید
if (typeof window.QuestionFormClasses.TagManager !== 'undefined') {
window.questionFormManager.tagManager = new window.QuestionFormClasses.TagManager();
}
if (typeof window.QuestionFormClasses.TextEditor !== 'undefined') {
window.questionFormManager.textEditor = new window.QuestionFormClasses.TextEditor();
}
if (typeof window.QuestionFormClasses.FileManager !== 'undefined') {
window.questionFormManager.fileManager = new window.QuestionFormClasses.FileManager();
}
// اعتبارسنجی فرم
const form = document.querySelector('form');
if (form) {
// حذف event listener قبلی اگر وجود دارد
form.removeEventListener('submit', handleFormSubmit);
form.addEventListener('submit', handleFormSubmit, false);
// اطمینان از اینکه form دارای data-turbo="false" است
form.setAttribute('data-turbo', 'false');
}
window.questionFormManager.initialized = true;
}
function handleFormSubmit(e) {
// اطمینان از اینکه تگ‌ها به فرم اضافه شده‌اند
if (window.questionFormManager.tagManager) {
// ابتدا تگ‌ها را به فرم اضافه کن
window.questionFormManager.tagManager.updateHiddenInput();
// اعتبارسنجی تگ‌ها
if (!window.questionFormManager.tagManager.validateTags()) {
e.preventDefault();
e.stopPropagation();
return false;
}
}
// اطمینان از اینکه فایل‌ها به فرم اضافه شده‌اند
if (window.questionFormManager.fileManager) {
window.questionFormManager.fileManager.updateFileInput();
}
// غیرفعال کردن Turbo برای این فرم
const form = e.target;
if (form) {
form.setAttribute('data-turbo', 'false');
}
const fileInput = form.querySelector('input[type="file"]');
console.log('Form submitted with files:', fileInput ? fileInput.files.length : 0);
// بررسی فایل‌های انتخاب شده در JavaScript
if (window.questionFormManager.fileManager) {
console.log('Selected files in JavaScript:', window.questionFormManager.fileManager.selectedFiles.length);
}
}
// اجرا در بارگذاری صفحه
document.addEventListener('DOMContentLoaded', function() {
// Reset initialization state
window.questionFormManager.initialized = false;
initializeQuestionForm();
});
document.addEventListener('turbo:load', function() {
// Reset initialization state for Turbo navigation
window.questionFormManager.initialized = false;
initializeQuestionForm();
});
</script>
{% endblock %}