mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 07:27:04 +03:00
Replace messages jQuery drag/drop with native HTML5
This commit is contained in:
parent
d88c183112
commit
c282e2c9b5
10 changed files with 91 additions and 149 deletions
120
dev/External/User/ko.js
vendored
120
dev/External/User/ko.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
const ko = window.ko,
|
const ko = window.ko,
|
||||||
$ = jQuery;
|
$ = jQuery,
|
||||||
|
validTransfer = data => ('move' === data.dropEffect || 'copy' === data.dropEffect) // effectAllowed
|
||||||
|
&& data.getData('text/x-rainloop-json');
|
||||||
|
|
||||||
ko.bindingHandlers.editor = {
|
ko.bindingHandlers.editor = {
|
||||||
init: (element, fValueAccessor) => {
|
init: (element, fValueAccessor) => {
|
||||||
|
|
@ -89,106 +91,40 @@ ko.bindingHandlers.emailsTags = {
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.bindingHandlers.draggable = {
|
ko.bindingHandlers.draggable = {
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
init: (element, fValueAccessor) => {
|
||||||
if (!rl.settings.app('mobile')) {
|
if (!rl.settings.app('mobile')) {
|
||||||
const triggerZone = 50,
|
element.addEventListener("dragstart", e => fValueAccessor()(e));
|
||||||
scrollSpeed = 3,
|
|
||||||
fAllValueFunc = fAllBindingsAccessor(),
|
|
||||||
selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '',
|
|
||||||
droppable = selector ? document.querySelector(selector) : null,
|
|
||||||
conf = {
|
|
||||||
distance: 20,
|
|
||||||
handle: '.dragHandle',
|
|
||||||
cursorAt: { top: 22, left: 3 },
|
|
||||||
refreshPositions: true,
|
|
||||||
scroll: true,
|
|
||||||
drag: null,
|
|
||||||
stop: null,
|
|
||||||
helper: null
|
|
||||||
};
|
|
||||||
let bcr;
|
|
||||||
|
|
||||||
if (droppable) {
|
|
||||||
conf.drag = event => {
|
|
||||||
if (droppable.scrollTopMax) {
|
|
||||||
clearInterval(droppable.timerScroll);
|
|
||||||
if (droppable.scrollTop
|
|
||||||
&& bcr.top < event.clientY
|
|
||||||
&& bcr.top + triggerZone > event.clientY)
|
|
||||||
{
|
|
||||||
droppable.timerScroll = setInterval(() => droppable.scrollTop -= scrollSpeed, 10);
|
|
||||||
}
|
|
||||||
else if (droppable.scrollTop < droppable.scrollTopMax
|
|
||||||
&& bcr.bottom > event.clientY
|
|
||||||
&& bcr.bottom - triggerZone < event.clientY)
|
|
||||||
{
|
|
||||||
droppable.timerScroll = setInterval(() => droppable.scrollTop += scrollSpeed, 10);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInterval(droppable.timerScroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
conf.stop = () => clearInterval(droppable.timerScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.helper = event => fValueAccessor()(event && event.target ? ko.dataFor(event.target) : null);
|
|
||||||
|
|
||||||
$(element)
|
|
||||||
.draggable(conf)
|
|
||||||
.on('mousedown.koDraggable', () => {
|
|
||||||
require('Common/Utils').removeInFocus();
|
|
||||||
bcr = droppable ? droppable.getBoundingClientRect() : null;
|
|
||||||
});
|
|
||||||
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () =>
|
|
||||||
$(element)
|
|
||||||
.off('mousedown.koDraggable')
|
|
||||||
.draggable('destroy')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.bindingHandlers.droppable = {
|
ko.bindingHandlers.droppable = {
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
init: (element, fValueAccessor) => {
|
||||||
if (!rl.settings.app('mobile')) {
|
if (!rl.settings.app('mobile')) {
|
||||||
const fValueFunc = fValueAccessor(),
|
const FolderList = fValueAccessor(),
|
||||||
fAllValueFunc = fAllBindingsAccessor(),
|
folder = ko.dataFor(element),
|
||||||
fOverCallback = fAllValueFunc && fAllValueFunc.droppableOver ? fAllValueFunc.droppableOver : null,
|
fnHover = e => {
|
||||||
fOutCallback = fAllValueFunc && fAllValueFunc.droppableOut ? fAllValueFunc.droppableOut : null,
|
if (validTransfer(e.dataTransfer)) {
|
||||||
conf = {
|
e.preventDefault();
|
||||||
tolerance: 'pointer',
|
element.classList.add('droppableHover');
|
||||||
hoverClass: 'droppableHover',
|
FolderList.messagesDropOver(folder);
|
||||||
drop: null,
|
}
|
||||||
over: null,
|
|
||||||
out: null
|
|
||||||
};
|
};
|
||||||
|
element.addEventListener("dragenter", fnHover);
|
||||||
if (fValueFunc) {
|
element.addEventListener("dragover", fnHover);
|
||||||
conf.drop = (event, ui) => {
|
element.addEventListener("dragleave", e => {
|
||||||
fValueFunc(event, ui);
|
e.preventDefault();
|
||||||
};
|
element.classList.remove('droppableHover');
|
||||||
|
FolderList.messagesDropOut();
|
||||||
if (fOverCallback) {
|
});
|
||||||
conf.over = (event, ui) => {
|
element.addEventListener("drop", e => {
|
||||||
fOverCallback(event, ui);
|
const data = JSON.parse(validTransfer(e.dataTransfer));
|
||||||
};
|
if (data) {
|
||||||
|
data.copy = data.copy && event.ctrlKey;
|
||||||
|
e.preventDefault();
|
||||||
|
FolderList.messagesDrop(folder, data);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (fOutCallback) {
|
|
||||||
conf.out = (event, ui) => {
|
|
||||||
fOutCallback(event, ui);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
$(element).droppable(conf);
|
|
||||||
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
|
||||||
$(element).droppable('destroy');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,10 @@
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.droppableHover * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -621,16 +621,15 @@ html .messageList .line-loading {
|
||||||
height: 5px !important;
|
height: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggablePlace {
|
#messagesDragImage {
|
||||||
z-index: 10003;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #333;
|
background-color: #000;
|
||||||
background-color: rgba(0,0,0,0.5);
|
|
||||||
padding: 4px 10px;
|
|
||||||
min-width: 30px;
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
cursor: pointer;
|
min-width: 30px;
|
||||||
cursor: move;
|
padding: 4px 10px;
|
||||||
|
position: fixed;
|
||||||
|
right: -100px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-mobile {
|
html.rl-mobile {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { Capa, Focused, Layout, KeyState } from 'Common/Enums';
|
import { Capa, Focused, Layout, KeyState } from 'Common/Enums';
|
||||||
import { $htmlCL, leftPanelDisabled, moveAction } from 'Common/Globals';
|
import { leftPanelDisabled, moveAction } from 'Common/Globals';
|
||||||
import { mailBox, settings } from 'Common/Links';
|
import { mailBox, settings } from 'Common/Links';
|
||||||
import { setFolderHash } from 'Common/Cache';
|
import { setFolderHash } from 'Common/Cache';
|
||||||
|
|
||||||
|
|
@ -218,18 +218,12 @@ class FolderListMailBoxUserView extends AbstractViewNext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FolderModel} toFolder
|
* @param {FolderModel} toFolder
|
||||||
* @param {{helper:jQuery}} ui
|
* @param {Array} data
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
messagesDrop(toFolder, ui) {
|
messagesDrop(toFolder, data) {
|
||||||
if (toFolder && ui && ui.helper) {
|
if (toFolder && data && data.folder && Array.isArray(data.uids)) {
|
||||||
const fromFolderFullNameRaw = ui.helper.rlFolder,
|
rl.app.moveMessagesToFolder(data.folder, data.uids, toFolder.fullNameRaw, data.copy);
|
||||||
copy = $htmlCL.contains('rl-ctrl-key-pressed'),
|
|
||||||
uids = ui.helper.rlUids;
|
|
||||||
|
|
||||||
if (fromFolderFullNameRaw && Array.isArray(uids)) {
|
|
||||||
rl.app.moveMessagesToFolder(fromFolderFullNameRaw, uids, toFolder.fullNameRaw, copy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ const
|
||||||
canBeMovedHelper = (self) => self.canBeMoved(),
|
canBeMovedHelper = (self) => self.canBeMoved(),
|
||||||
ifvisible = window.ifvisible;
|
ifvisible = window.ifvisible;
|
||||||
|
|
||||||
|
let dragImage;
|
||||||
|
|
||||||
@view({
|
@view({
|
||||||
name: 'View/User/MailBox/MessageList',
|
name: 'View/User/MailBox/MessageList',
|
||||||
type: ViewType.Right,
|
type: ViewType.Right,
|
||||||
|
|
@ -479,30 +481,38 @@ class MessageListMailBoxUserView extends AbstractViewNext {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragAndDronHelper(oMessageListItem) {
|
dragStart(event) {
|
||||||
if (oMessageListItem) {
|
const item = ko.dataFor(document.elementFromPoint(event.clientX, event.clientY));
|
||||||
oMessageListItem.checked(true);
|
item && item.checked(true);
|
||||||
|
|
||||||
|
dragImage || (dragImage = document.getElementById('messagesDragImage'));
|
||||||
|
|
||||||
|
const uids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails();
|
||||||
|
if (dragImage && uids.length) {
|
||||||
|
dragImage.querySelector('.text').textContent = uids.length;
|
||||||
|
let img = dragImage.querySelector('.icon-white');
|
||||||
|
img.classList.toggle('icon-copy', event.ctrlKey);
|
||||||
|
img.classList.toggle('icon-mail', !event.ctrlKey);
|
||||||
|
|
||||||
|
// Else Chrome doesn't show it
|
||||||
|
dragImage.style.left = event.clientX + 'px';
|
||||||
|
dragImage.style.top = event.clientY + 'px';
|
||||||
|
dragImage.style.right = 'auto';
|
||||||
|
|
||||||
|
let action = event.ctrlKey ? 'copy' : 'move';
|
||||||
|
event.dataTransfer.setData('text/x-rainloop-json', JSON.stringify({
|
||||||
|
copy: event.ctrlKey,
|
||||||
|
folder: FolderStore.currentFolderFullNameRaw(),
|
||||||
|
uids: uids
|
||||||
|
}));
|
||||||
|
event.dataTransfer.setDragImage(dragImage, 0, 0);
|
||||||
|
event.dataTransfer.effectAllowed = action;
|
||||||
|
|
||||||
|
// Remove the Chrome visibility
|
||||||
|
dragImage.style.cssText = '';
|
||||||
|
} else {
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = Element.fromHTML('<div class="draggablePlace">' +
|
|
||||||
'<span class="text"></span> ' +
|
|
||||||
'<i class="icon-copy icon-white visible-on-ctrl"></i>' +
|
|
||||||
'<i class="icon-mail icon-white hidden-on-ctrl"></i>' +
|
|
||||||
'</div>'),
|
|
||||||
updateUidsInfo = () => {
|
|
||||||
const uids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails();
|
|
||||||
el.rlUids = uids;
|
|
||||||
el.querySelector('.text').textContent = uids.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('rl-hidden').append(el);
|
|
||||||
|
|
||||||
el.rlFolder = FolderStore.currentFolderFullNameRaw();
|
|
||||||
|
|
||||||
updateUidsInfo();
|
|
||||||
setTimeout(updateUidsInfo,1);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
<div class="e-item" data-bind="visible: visible, css: { 'i-am-inbox-wrapper': isInbox }">
|
<div class="e-item" data-bind="visible: visible, css: { 'i-am-inbox-wrapper': isInbox }">
|
||||||
<a class="e-link" data-bind="droppable: function (oEvent, oUi) { $root.messagesDrop($data, oUi); },
|
<a class="e-link" data-bind="droppable: $root,
|
||||||
droppableOver: function (oEvent, oUi) { $root.messagesDropOver($data, oUi); },
|
|
||||||
droppableOut: function (oEvent, oUi) { $root.messagesDropOut($data, oUi); },
|
|
||||||
css: { 'i-am-inbox': isInbox, 'selected': selected() && !isSystemFolder(), 'selectable': selectableForFolderList, 'hidden' : hidden, 'print-count': hasUnreadMessages, 'unread-sub': hasSubScribedUnreadMessagesSubfolders, 'system': isSystemFolder, 'anim-action-class': actionBlink }">
|
css: { 'i-am-inbox': isInbox, 'selected': selected() && !isSystemFolder(), 'selectable': selectableForFolderList, 'hidden' : hidden, 'print-count': hasUnreadMessages, 'unread-sub': hasSubScribedUnreadMessagesSubfolders, 'system': isSystemFolder, 'anim-action-class': actionBlink }">
|
||||||
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
|
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
|
||||||
<i data-bind="css: collapsedCss()"></i>
|
<i data-bind="css: collapsedCss()"></i>
|
||||||
|
|
@ -9,4 +7,4 @@
|
||||||
<span class="name" data-bind="text: name"></span>
|
<span class="name" data-bind="text: name"></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="b-sub-folders" data-bind="template: { name: 'MailFolderListItem', foreach: subFolders }, css: { 'collapsed': collapsed() }"></div>
|
<div class="b-sub-folders" data-bind="template: { name: 'MailFolderListItem', foreach: subFolders }, css: { 'collapsed': collapsed() }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="e-item b-folder-system-item">
|
<div class="e-item b-folder-system-item">
|
||||||
<a class="e-link" data-bind="droppable: function (oEvent, oUi) { $root.messagesDrop($data, oUi); }, css: { 'selected': selected, 'selectable': selectable, 'print-count': 0 < printableUnreadCount().length, 'anim-action-class': actionBlink, 'is-inbox': isInbox }">
|
<a class="e-link" data-bind="droppable: $root, css: { 'selected': selected, 'selectable': selectable, 'print-count': 0 < printableUnreadCount().length, 'anim-action-class': actionBlink, 'is-inbox': isInbox }">
|
||||||
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
|
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
|
||||||
|
|
||||||
<span class="focused-poiner"></span>
|
<span class="focused-poiner"></span>
|
||||||
|
|
@ -9,4 +9,4 @@
|
||||||
<i class="icon-star-empty"></i>
|
<i class="icon-star-empty"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -198,9 +198,10 @@
|
||||||
<div class="listEmptySearchList" data-bind="visible: !dragOver() && 0 === messageList().length && !messageListCompleteLoadingThrottle() && '' === messageListError() && '' !== messageListSearch()">
|
<div class="listEmptySearchList" data-bind="visible: !dragOver() && 0 === messageList().length && !messageListCompleteLoadingThrottle() && '' === messageListError() && '' !== messageListSearch()">
|
||||||
<span class="i18n" data-i18n="MESSAGE_LIST/EMPTY_SEARCH_LIST"></span>
|
<span class="i18n" data-i18n="MESSAGE_LIST/EMPTY_SEARCH_LIST"></span>
|
||||||
</div>
|
</div>
|
||||||
<div data-bind="draggable: dragAndDronHelper, droppableSelector: '.b-folders .b-content'">
|
<div draggable="true" data-bind="draggable: dragStart">
|
||||||
<div class="messageListPlace" data-bind="template: { name: messageListItemTemplate(), foreach: messageList }"></div>
|
<div class="messageListPlace" data-bind="template: { name: messageListItemTemplate(), foreach: messageList }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="messagesDragImage"><span class="text"></span> <i class="icon-mail icon-white"></i></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<i class="icon-star-empty"></i>
|
<i class="icon-star-empty"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dateParent actionHandle dragHandle">
|
<div class="dateParent actionHandle">
|
||||||
<time class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></time>
|
<time class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></time>
|
||||||
</div>
|
</div>
|
||||||
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
|
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
|
||||||
|
|
@ -29,18 +29,18 @@
|
||||||
<div class="checkedParent">
|
<div class="checkedParent">
|
||||||
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
|
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="senderParent actionHandle dragHandle">
|
<div class="senderParent actionHandle">
|
||||||
<span class="replyFlag"><i class="icon-reply"></i> </span>
|
<span class="replyFlag"><i class="icon-reply"></i> </span>
|
||||||
<span class="forwardFlag"><i class="icon-forward"></i> </span>
|
<span class="forwardFlag"><i class="icon-forward"></i> </span>
|
||||||
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
|
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="attachmentParent actionHandle dragHandle">
|
<div class="attachmentParent actionHandle">
|
||||||
<span class="attachment">
|
<span class="attachment">
|
||||||
<i data-bind="css: attachmentIconClass"></i>
|
<i data-bind="css: attachmentIconClass"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="subjectParent actionHandle dragHandle">
|
<div class="subjectParent actionHandle">
|
||||||
<b class="importantMark">!</b>
|
<b class="importantMark">!</b>
|
||||||
<span class="subject emptySubjectText" data-bind="text: $root.emptySubjectValue"></span>
|
<span class="subject emptySubjectText" data-bind="text: $root.emptySubjectValue"></span>
|
||||||
<span class="subject-prefix" data-bind="text: subjectPrefix"></span><span class="subject-suffix" data-bind="text: subjectSuffix"></span>
|
<span class="subject-prefix" data-bind="text: subjectPrefix"></span><span class="subject-suffix" data-bind="text: subjectSuffix"></span>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="delimiter"></div>
|
<div class="delimiter"></div>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="dateParent actionHandle dragHandle">
|
<div class="dateParent actionHandle">
|
||||||
<time class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></time>
|
<time class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></time>
|
||||||
</div>
|
</div>
|
||||||
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
|
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
|
||||||
|
|
@ -29,18 +29,18 @@
|
||||||
<i class="icon-star-empty"></i>
|
<i class="icon-star-empty"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="senderParent actionHandle dragHandle">
|
<div class="senderParent actionHandle">
|
||||||
<span class="replyFlag"><i class="icon-reply"></i> </span>
|
<span class="replyFlag"><i class="icon-reply"></i> </span>
|
||||||
<span class="forwardFlag"><i class="icon-forward"></i> </span>
|
<span class="forwardFlag"><i class="icon-forward"></i> </span>
|
||||||
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
|
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="attachmentParent actionHandle dragHandle">
|
<div class="attachmentParent actionHandle">
|
||||||
<span class="attachment">
|
<span class="attachment">
|
||||||
<i data-bind="css: attachmentIconClass"></i>
|
<i data-bind="css: attachmentIconClass"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="subjectParent actionHandle dragHandle">
|
<div class="subjectParent actionHandle">
|
||||||
<b class="importantMark">!</b>
|
<b class="importantMark">!</b>
|
||||||
<span class="subject emptySubjectText" data-bind="text: $root.emptySubjectValue"></span>
|
<span class="subject emptySubjectText" data-bind="text: $root.emptySubjectValue"></span>
|
||||||
<span class="subject-prefix" data-bind="text: subjectPrefix"></span><span class="subject-suffix" data-bind="text: subjectSuffix"></span>
|
<span class="subject-prefix" data-bind="text: subjectPrefix"></span><span class="subject-suffix" data-bind="text: subjectSuffix"></span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue