2025-09-05 09:37:27 +03:30
|
|
|
{% extends 'base.html.twig' %}
|
|
|
|
|
|
|
|
|
|
{% block title %}پاسخ به سوال: {{ question.title }} - پرسش و پاسخ{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block body %}
|
2025-09-05 18:03:55 +03:30
|
|
|
<main class="min-h-screen bg-gray-50">
|
|
|
|
|
<div class="container mx-auto px-4 py-8">
|
|
|
|
|
<div class="max-w-4xl mx-auto">
|
2025-09-05 09:37:27 +03:30
|
|
|
<!-- نمایش سوال -->
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="bg-white rounded-2xl shadow-soft mb-8 overflow-hidden">
|
|
|
|
|
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-8 py-6 border-b border-gray-200">
|
|
|
|
|
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
|
|
|
|
|
<svg class="w-6 h-6 text-blue-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
سوال:
|
|
|
|
|
</h2>
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="p-8">
|
|
|
|
|
<h3 class="text-2xl font-bold text-gray-900 mb-4">{{ question.title }}</h3>
|
|
|
|
|
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed mb-6">
|
|
|
|
|
{{ question.content|markdown|raw }}
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
{% for tagRelation in question.tagRelations %}
|
|
|
|
|
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
|
|
|
|
|
class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
|
|
|
|
|
{{ tagRelation.tag.name }}
|
|
|
|
|
</a>
|
|
|
|
|
{% endfor %}
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- فرم پاسخ -->
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
|
|
|
|
|
<div class="bg-gradient-to-r from-green-50 to-emerald-50 px-8 py-6 border-b border-gray-200">
|
|
|
|
|
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
|
|
|
|
|
<svg class="w-6 h-6 text-green-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
2025-09-05 09:37:27 +03:30
|
|
|
<polyline points="9,17 4,12 9,7"></polyline>
|
|
|
|
|
<path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>
|
2025-09-05 18:03:55 +03:30
|
|
|
</svg>
|
|
|
|
|
پاسخ شما
|
|
|
|
|
</h2>
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="p-8">
|
|
|
|
|
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-6'}}) }}
|
2025-09-05 09:37:27 +03:30
|
|
|
|
2025-09-05 18:03:55 +03:30
|
|
|
<div>
|
|
|
|
|
{{ form_label(form.content, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
|
|
|
|
<div class="qa-editor-toolbar border border-gray-300 border-b-0 rounded-t-xl p-3 bg-gray-50">
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
<button type="button" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('bold')" title="پررنگ">
|
|
|
|
|
<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" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('italic')" title="کج">
|
|
|
|
|
<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" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('code')" title="کد">
|
|
|
|
|
<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" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="insertList()" title="لیست">
|
|
|
|
|
<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>
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
{{ 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': 8}}) }}
|
2025-09-05 09:37:27 +03:30
|
|
|
{{ form_errors(form.content) }}
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="mt-2 text-sm text-gray-600">
|
2025-09-05 09:37:27 +03:30
|
|
|
پاسخ خود را به صورت کامل و مفصل ارائه دهید. هرچه پاسخ شما دقیقتر باشد، برای دیگران مفیدتر خواهد بود.
|
2025-09-05 18:03:55 +03:30
|
|
|
<br><span class="text-gray-500">میتوانید از دکمههای بالا برای فرمت کردن متن استفاده کنید.</span>
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-09-05 18:03:55 +03:30
|
|
|
<!-- پیوست فایل -->
|
|
|
|
|
<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">
|
|
|
|
|
<a href="{{ path('qa_question_show', {'id': question.id}) }}"
|
|
|
|
|
class="inline-flex items-center 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-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
انصراف
|
2025-09-05 09:37:27 +03:30
|
|
|
</a>
|
2025-09-05 18:03:55 +03:30
|
|
|
<button type="submit"
|
|
|
|
|
class="inline-flex items-center 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-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
ارسال پاسخ
|
2025-09-05 09:37:27 +03:30
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{{ form_end(form) }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- راهنمای پاسخ دادن -->
|
2025-09-05 18:03:55 +03:30
|
|
|
<div class="bg-white rounded-2xl shadow-soft mt-8 overflow-hidden">
|
|
|
|
|
<div class="bg-gradient-to-r from-yellow-50 to-orange-50 px-8 py-6 border-b border-gray-200">
|
|
|
|
|
<h3 class="text-xl font-bold text-gray-900 flex items-center">
|
|
|
|
|
<svg class="w-6 h-6 text-yellow-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
راهنمای پاسخ دادن
|
|
|
|
|
</h3>
|
2025-09-05 09:37:27 +03:30
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
<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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" 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>
|
2025-09-05 09:37:27 +03:30
|
|
|
</ul>
|
|
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
<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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" 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-start">
|
|
|
|
|
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" 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>
|
2025-09-05 09:37:27 +03:30
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-05 18:03:55 +03:30
|
|
|
</main>
|
2025-09-05 09:37:27 +03:30
|
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
// توابع ادیتور متن
|
|
|
|
|
function formatText(command) {
|
2025-09-05 18:03:55 +03:30
|
|
|
const textarea = document.querySelector('textarea[name="answer[content]"]');
|
2025-09-05 09:37:27 +03:30
|
|
|
const start = textarea.selectionStart;
|
|
|
|
|
const end = textarea.selectionEnd;
|
|
|
|
|
const selectedText = textarea.value.substring(start, end);
|
|
|
|
|
|
|
|
|
|
let formattedText = '';
|
|
|
|
|
|
|
|
|
|
switch(command) {
|
|
|
|
|
case 'bold':
|
|
|
|
|
formattedText = `**${selectedText}**`;
|
|
|
|
|
break;
|
|
|
|
|
case 'italic':
|
|
|
|
|
formattedText = `*${selectedText}*`;
|
|
|
|
|
break;
|
|
|
|
|
case 'code':
|
|
|
|
|
formattedText = `\`${selectedText}\``;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textarea.value = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end);
|
|
|
|
|
textarea.focus();
|
|
|
|
|
textarea.setSelectionRange(start + formattedText.length, start + formattedText.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function insertList() {
|
2025-09-05 18:03:55 +03:30
|
|
|
const textarea = document.querySelector('textarea[name="answer[content]"]');
|
2025-09-05 09:37:27 +03:30
|
|
|
const start = textarea.selectionStart;
|
|
|
|
|
const end = textarea.selectionEnd;
|
|
|
|
|
const selectedText = textarea.value.substring(start, end);
|
|
|
|
|
|
|
|
|
|
const listText = selectedText.split('\n').map(line => `- ${line}`).join('\n');
|
|
|
|
|
textarea.value = textarea.value.substring(0, start) + listText + textarea.value.substring(end);
|
|
|
|
|
textarea.focus();
|
|
|
|
|
}
|
2025-09-05 18:03:55 +03:30
|
|
|
|
|
|
|
|
// مدیریت فایلها
|
|
|
|
|
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() {
|
|
|
|
|
this.fileInput = document.getElementById('file-input');
|
|
|
|
|
this.selectedFilesDiv = document.getElementById('selected-files');
|
|
|
|
|
this.fileListDiv = document.getElementById('file-list');
|
|
|
|
|
|
|
|
|
|
if (!this.fileInput) return;
|
|
|
|
|
|
|
|
|
|
this.bindEvents();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindEvents() {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateFile(file) {
|
|
|
|
|
// بررسی تعداد فایلها
|
|
|
|
|
if (this.selectedFiles.length >= this.maxFiles) {
|
|
|
|
|
alert(`حداکثر ${this.maxFiles} فایل میتوانید انتخاب کنید`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// بررسی نوع فایل
|
|
|
|
|
if (!this.allowedTypes.includes(file.type)) {
|
|
|
|
|
alert('فرمت فایل مجاز نیست');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// بررسی حجم فایل
|
|
|
|
|
if (file.size > this.maxFileSize) {
|
|
|
|
|
alert('حجم فایل نمیتواند بیش از 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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() {
|
|
|
|
|
// ایجاد DataTransfer object جدید
|
|
|
|
|
const dt = new DataTransfer();
|
|
|
|
|
this.selectedFiles.forEach(fileData => {
|
|
|
|
|
dt.items.add(fileData.file);
|
|
|
|
|
});
|
|
|
|
|
this.fileInput.files = dt.files;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// متغیرهای سراسری
|
|
|
|
|
let fileManager;
|
|
|
|
|
|
|
|
|
|
// مقداردهی اولیه
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
fileManager = new FileManager();
|
|
|
|
|
});
|
2025-09-05 09:37:27 +03:30
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|