[ADD] PMS: Summernote inside activity comment in OKR
This commit is contained in:
@@ -552,7 +552,7 @@ class EmployeeObjective(HorillaModel):
|
||||
class Comment(models.Model):
|
||||
"""comments for objectives"""
|
||||
|
||||
comment = models.CharField(max_length=150)
|
||||
comment = models.TextField()
|
||||
employee_id = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.DO_NOTHING,
|
||||
@@ -572,6 +572,9 @@ class Comment(models.Model):
|
||||
objects = HorillaCompanyManager(
|
||||
related_company_field="employee_id__employee_work_info__company_id"
|
||||
)
|
||||
xss_exempt_fields = [
|
||||
"comment",
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee_id.employee_first_name} - {self.comment} "
|
||||
|
||||
@@ -1,177 +1,184 @@
|
||||
{% load i18n %} {% load pmsfilters %}
|
||||
|
||||
<style>
|
||||
#commentEditor .note-btn-group.send-group {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{ message.tags }}">{{ message }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="oh-activity-sidebar__header">
|
||||
<a
|
||||
style="cursor: pointer;"
|
||||
onclick="$('.oh-activity-sidebar--show').removeClass('oh-activity-sidebar--show');" >
|
||||
<ion-icon
|
||||
name="chevron-forward-outline"
|
||||
class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close"
|
||||
data-target="#leaveactivitySidebar"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<span class="oh-activity-sidebar__title"> {% trans "Activities" %} </span>
|
||||
<a style="cursor: pointer;" onclick="$('.oh-activity-sidebar--show').removeClass('oh-activity-sidebar--show');"><ion-icon name="chevron-forward-outline" class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close" data-target="#leaveactivitySidebar"></ion-icon></a>
|
||||
<span class="oh-activity-sidebar__title">{% trans 'Activities' %}</span>
|
||||
</div>
|
||||
<form method="post"
|
||||
hx-target="#activityContainer"
|
||||
hx-post=" {% url 'objective-detailed-view-comment' objective.id %} " id="commentForm">
|
||||
{% csrf_token %}
|
||||
<form method="post" hx-target="#activityContainer" hx-post=" {% url 'objective-detailed-view-comment' objective.id %} " id="commentForm">
|
||||
{% csrf_token %}
|
||||
|
||||
<div>
|
||||
<input type="text" name="comment" id="commentInput" class="oh-input w-100" placeholder="Comment here">
|
||||
<button type="submit" id="commentButton" class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp" style="display: none;">
|
||||
{% trans "Comment" %}
|
||||
</button>
|
||||
</div>
|
||||
<div id="commentEditor">
|
||||
<textarea rows="2" cols="2" name="comment" id="editor"></textarea>
|
||||
<button type="submit" id="commentButton" class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp" style="display: none;">{% trans 'Comment' %}</button>
|
||||
</div>
|
||||
|
||||
<div class="oh-inner-sidebar-content__footer"></div>
|
||||
<div class="oh-inner-sidebar-content__footer"></div>
|
||||
</form>
|
||||
<ul class="oh-activity-list ">
|
||||
{% comment %} <li class="oh-activity-list__comment-item">
|
||||
<!-- comment section -->
|
||||
{% for comment in comments %}
|
||||
<div class="oh-activity-list__comment-title">
|
||||
<ul class="oh-activity-list">
|
||||
{% comment %} <li class="oh-activity-list__comment-item">
|
||||
<!-- comment section -->
|
||||
{% for comment in comments %}
|
||||
<div class="oh-activity-list__comment-title">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{ comment.employee_id }}&background=random" class="oh-activity-list__image" alt="Simone de Beauvoir" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description">
|
||||
<span><strong>{{ comment.employee_id }}</strong> {% trans 'added a comment' %}</span>
|
||||
<div class="oh-activity-list__comment-timestamp mt-1">
|
||||
<span class="dateformat_changer">{{ comment.created_at|date:'M. d, Y' }}</span> , 
|
||||
<span class="timeformat_changer">{{ comment.created_at|date:'g:i a' }}</span>
|
||||
</div>
|
||||
</small>
|
||||
</div>
|
||||
<div class="oh-activity-list__comment-container">
|
||||
<p class="oh-activity-list__comment">{{ comment.comment }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- end of comment -->
|
||||
</li> {% endcomment %}
|
||||
<!-- history section -->
|
||||
{% for activity in activity_list %}
|
||||
{% comment %} {% for history in history_object.delta.changes %}
|
||||
{% endcomment %}
|
||||
{% if activity.type == 'Changes' %}
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{comment.employee_id}}&background=random"
|
||||
class="oh-activity-list__image" alt="Simone de Beauvoir" />
|
||||
<img src="https://ui-avatars.com/api/?name={{ activity.updated_by }}&background=random" class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description">
|
||||
<span><strong>{{comment.employee_id}}</strong> {% trans "added a comment" %}</span>
|
||||
<div class="oh-activity-list__comment-timestamp mt-1">
|
||||
<span class="dateformat_changer">{{ comment.created_at|date:"M. d, Y" }}</span> , 
|
||||
<span class="timeformat_changer">{{ comment.created_at|date:"g:i a" }}</span>
|
||||
</div>
|
||||
<strong>{{ activity.updated_by }}</strong>
|
||||
{% trans 'updated' %}
|
||||
<strong>{{ activity.changes.0.field|title|cut:'_' }}</strong> {% trans 'from' %}
|
||||
<strong>{{ activity.changes.0.old }}</strong> to <strong>{{ activity.changes.0.new }}</strong>
|
||||
</small>
|
||||
</div>
|
||||
<div class="oh-activity-list__comment-container">
|
||||
<p class="oh-activity-list__comment">
|
||||
{{comment.comment}}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.pair.0.history_date|date:'M. d, Y' }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.pair.0.history_date|date:'g:i a' }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% elif activity.type == 'key_result' %}
|
||||
{% for history in activity.key_result.delta.changes %}
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{ activity.key_result.changed_user }}&background=random" class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description">
|
||||
<strong>{{ activity.key_result.changed_user }}</strong>
|
||||
{% trans 'updated' %}
|
||||
<strong>{{ history.field|replace|title }}</strong> {% trans 'field of ' %}
|
||||
<strong>{{ activity.key_result.k_r }}</strong> {% trans 'key result' %},{% trans 'from' %}
|
||||
<strong>{{ history.old }}</strong> {% trans 'to' %} <strong>{{ history.new }}</strong>
|
||||
{% comment %} <strong>{{ activity.changes.0.field|title|cut:'_' }}</strong> {% trans 'from' %}
|
||||
<strong>{{ activity.changes.0.old }}</strong> to <strong>{{ activity.changes.0.new }}</strong> {% endcomment %}
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.date|date:'M. d, Y' }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.date|date:'g:i a' }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<!-- end of comment -->
|
||||
</li> {% endcomment %}
|
||||
<!-- history section -->
|
||||
{% for activity in activity_list %}
|
||||
|
||||
{% comment %} {% for history in history_object.delta.changes %} {% endcomment %}
|
||||
{% if activity.type == 'Changes' %}
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{activity.updated_by}}&background=random"
|
||||
class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description"><strong>{{activity.updated_by}} </strong>
|
||||
{% trans "updated" %}
|
||||
<strong> {{activity.changes.0.field |title|cut:'_'}}</strong> {% trans "from" %}
|
||||
<strong>{{activity.changes.0.old}}</strong> to <strong>{{activity.changes.0.new}}</strong>
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.pair.0.history_date|date:"M. d, Y" }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.pair.0.history_date|date:"g:i a" }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% elif activity.type == 'key_result' %}
|
||||
{% for history in activity.key_result.delta.changes %}
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{activity.key_result.changed_user}}&background=random"
|
||||
class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description"><strong>{{activity.key_result.changed_user}} </strong>
|
||||
{% trans "updated" %}
|
||||
<strong>{{history.field|replace|title}}</strong> {% trans "field of " %}
|
||||
<strong>{{activity.key_result.k_r}} </strong> {% trans "key result" %},
|
||||
{% trans "from" %}
|
||||
<strong>{{history.old}}</strong> {% trans "to" %} <strong>{{history.new}}</strong>
|
||||
{% comment %} <strong> {{activity.changes.0.field |title|cut:'_'}}</strong> {% trans "from" %}
|
||||
<strong>{{activity.changes.0.old}}</strong> to <strong>{{activity.changes.0.new}}</strong> {% endcomment %}
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.date|date:"M. d, Y" }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.date|date:"g:i a" }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% elif activity.type == 'comment' %}
|
||||
<ul class="pt-3 border-top">
|
||||
<div class="oh-activity-list__comment-title d-flex justify-content-between align-items-center">
|
||||
<div class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{activity.comment.employee_id}}&background=random"
|
||||
class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description">
|
||||
<span><strong>{{activity.comment.employee_id}}</strong> {% trans "added a comment" %}</span>
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.comment.created_at|date:"M. d, Y" }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.comment.created_at|date:"g:i a" }}</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-activity-list__comment-container">
|
||||
<p class="oh-activity-list__comment">
|
||||
{{activity.comment.comment}}
|
||||
</p>
|
||||
</div>
|
||||
</ul>
|
||||
{% else %}
|
||||
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{activity.updated_by}}&background=random"
|
||||
class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description"><strong>{{activity.updated_by}} </strong>
|
||||
{% trans "Created Objective" %}
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.pair.0.history_date|date:"M. d, Y" }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.pair.0.history_date|date:"g:i a" }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% comment %} {% endfor %} {% endcomment %}
|
||||
{% endfor %}
|
||||
{% elif activity.type == 'comment' %}
|
||||
<ul class="pt-3 border-top">
|
||||
<div class="oh-activity-list__comment-title d-flex justify-content-between align-items-center">
|
||||
<div class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{ activity.comment.employee_id }}&background=random" class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description"><span><strong>{{ activity.comment.employee_id }}</strong> {% trans 'added a comment' %}</span></small>
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.comment.created_at|date:'M. d, Y' }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.comment.created_at|date:'g:i a' }}</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-activity-list__comment-container">
|
||||
<p class="oh-activity-list__comment">{{ activity.comment.comment|safe }}</p>
|
||||
</div>
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="d-flex justify-content-between align-items-center pt-3 mb-3 border-top">
|
||||
<li class="oh-activity-list__item align-items-center mb-0">
|
||||
<div class="oh-activity-list__photo oh-activity-list__photo--small me-2">
|
||||
<img src="https://ui-avatars.com/api/?name={{ activity.updated_by }}&background=random" class="oh-activity-list__image" alt="Albert Camus" />
|
||||
</div>
|
||||
<small class="oh-activity-list__description">
|
||||
<strong>{{ activity.updated_by }}</strong>
|
||||
{% trans 'Created Objective' %}
|
||||
</small>
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<span class="dateformat_changer">{{ activity.pair.0.history_date|date:'M. d, Y' }}</span> , 
|
||||
<span class="timeformat_changer">{{ activity.pair.0.history_date|date:'g:i a' }}</span>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% comment %}
|
||||
{% endfor %} {% endcomment %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
// Get references to the input field and comment button
|
||||
const commentInput = document.getElementById('commentInput');
|
||||
const commentButton = document.getElementById('commentButton');
|
||||
// ✅ Send Button styled like toolbar icon
|
||||
var sendButton = function (context) {
|
||||
var ui = $.summernote.ui
|
||||
return ui
|
||||
.button({
|
||||
contents: 'Add comment <ion-icon name="paper-plane-outline"></ion-icon>',
|
||||
tooltip: 'Send Content',
|
||||
click: function () {
|
||||
const content = context.invoke('code')
|
||||
$("#commentEditor button[type=submit]").click()
|
||||
}
|
||||
})
|
||||
.render()
|
||||
}
|
||||
|
||||
// Add event listener to the input field
|
||||
commentInput.addEventListener('input', function() {
|
||||
// Show the comment button if the input field is not empty, hide it otherwise
|
||||
if (commentInput.value.trim() !== '') {
|
||||
commentButton.style.display = 'inline';
|
||||
} else {
|
||||
commentButton.style.display = 'none';
|
||||
}
|
||||
});
|
||||
// ✅ Summernote Init
|
||||
$('#editor').summernote({
|
||||
height: 70, // compact height
|
||||
placeholder: 'Write something...',
|
||||
toolbar: [
|
||||
['insert', ['picture', 'link', 'pdfUpload']],
|
||||
['sendGroup', ['send']]
|
||||
],
|
||||
buttons: {
|
||||
pdfUpload: pdfUploadButton,
|
||||
send: sendButton,
|
||||
}
|
||||
})
|
||||
|
||||
console.log($("#commentEditor .note-btn-group.send-group").parent())
|
||||
|
||||
// ✅ Align last group to right
|
||||
$(document).ready(function () {
|
||||
$('.note-toolbar .note-btn-group').last().addClass('send-group')
|
||||
$("#commentEditor .note-btn-group.send-group").parent().addClass("d-flex justify-content-between")
|
||||
})
|
||||
</script>
|
||||
|
||||
27
static/build/js/summernote-lite.min.js
vendored
27
static/build/js/summernote-lite.min.js
vendored
@@ -26,21 +26,34 @@ function handlePDFUploadMultiple(files, context) {
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const base64 = e.target.result;
|
||||
const base64 = e.target.result; // full base64 data URL like "data:application/pdf;base64,...."
|
||||
const fileName = file.name;
|
||||
const uniqueId = `pdfModal_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
||||
const base64Content = base64.split(',')[1];
|
||||
const binary = atob(base64Content);
|
||||
const len = binary.length;
|
||||
const buffer = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
buffer[i] = binary.charCodeAt(i);
|
||||
}
|
||||
|
||||
const blob = new Blob([buffer], { type: 'application/pdf' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
|
||||
|
||||
const cardHTML = `
|
||||
<div style="border:1px solid #ccc; padding:10px; border-radius:6px; display:flex; align-items:center; gap:10px; margin:10px 0;width:200px;overflow:hidden;">
|
||||
<img src="https://cdn-icons-png.flaticon.com/512/337/337946.png" alt="PDF" width="40" />
|
||||
<img src="/static/images/ui/document-icon.png" alt="PDF" width="40" />
|
||||
<a href="javascript:void(0);" onclick="openPDFModal('${uniqueId}')" style="text-decoration:none; color:#007bff;">
|
||||
${fileName}
|
||||
</a>
|
||||
</div>
|
||||
<div id="${uniqueId}" class="custom-pdf-modal" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
|
||||
<div id="${uniqueId}" class="custom-pdf-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
|
||||
<div style="background:white; padding:20px; border-radius:8px; position:relative; width:80%; height:90%;">
|
||||
<button onclick="closePDFModal('${uniqueId}')" style="position:absolute; top:10px; right:10px; font-size:18px; background:#dc3545; color:white; border:none; border-radius:4px; padding:4px 10px;">×</button>
|
||||
<iframe src="${base64}" width="100%" height="100%" style="border:none;"></iframe>
|
||||
<iframe src="${blobUrl}" width="100%" height="100%" style="border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -80,16 +93,16 @@ function handlePDFUpload(file, context) {
|
||||
max-width:100%;
|
||||
white-space:nowrap;
|
||||
">
|
||||
<img src="https://cdn-icons-png.flaticon.com/512/337/337946.png" alt="PDF" width="20" />
|
||||
<img src="/static/images/ui/document-icon.png" alt="PDF" width="20" />
|
||||
<a href="javascript:void(0);" onclick="openPDFModal('${uniqueId}')"
|
||||
style="text-decoration:none; color:#007bff; font-weight:500;">
|
||||
${fileName}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
<div id="${uniqueId}" class="custom-pdf-modal" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
|
||||
<div id="${uniqueId}" class="custom-pdf-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
|
||||
<div style="background:white; padding:20px; border-radius:8px; position:relative; width:80%; height:90%;">
|
||||
<button onclick="closePDFModal('${uniqueId}')" style="position:absolute; top:10px; right:10px; font-size:18px; background:#dc3545; color:white; border:none; border-radius:4px; padding:4px 10px;">×</button>
|
||||
<button type="button" onclick="closePDFModal('${uniqueId}')" style="position:absolute; top:10px; right:10px; font-size:18px; background:#dc3545; color:white; border:none; border-radius:4px; padding:4px 10px;">×</button>
|
||||
<iframe src="${base64}" width="100%" height="100%" style="border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
BIN
static/images/ui/document-icon.png
Normal file
BIN
static/images/ui/document-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
Reference in New Issue
Block a user