Initial commit: Angular frontend and Expressjs backend

This commit is contained in:
chamikaJ
2024-05-17 09:32:30 +05:30
parent eb0a0d77d6
commit 298ca6beeb
3548 changed files with 193558 additions and 3 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,812 @@
/*
* Contentediable Mentiony jQuery plugin
* Version 0.1.0
* Written by: Luat Nguyen(luatnd) on 2016/05/27
*
* Transform textarea or input into contentediable with mention feature.
*
* License: MIT License - http://www.opensource.org/licenses/mit-license.php
*/
var tmpEle = null;
(function ($) {
var KEY = {
AT: 64,
BACKSPACE: 8,
DELETE: 46,
TAB: 9,
ESC: 27,
RETURN: 13,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
SPACE: 32,
HOME: 36,
END: 35,
COMMA: 188,
NUMPAD_ADD: 107,
NUMPAD_DECIMAL: 110,
NUMPAD_DIVIDE: 111,
NUMPAD_ENTER: 108,
NUMPAD_MULTIPLY: 106,
NUMPAD_SUBTRACT: 109,
PAGE_DOWN: 34,
PAGE_UP: 33,
PERIOD: 190,
};
jQuery.fn.mentiony = function (method, options) {
var defaults = {
debug: 0, // Set 1 to see console log message of this plugin
/**
* True [default] will make mention area size equal to initial size of textarea. NOTE: Textarea must visible on document ready if this value is true.
* False will not specify the CSS width attribute to every mention area.
*/
applyInitialSize: true,
globalTimeout: null, // Don't overwrite this config
timeOut: 400, // Do mention only when user input idle time > this value
triggerChar: '@', // @keyword-to-mention
/**
* Function for mention data processing
* @param mode
* @param keyword
* @param onDataRequestCompleteCallback
*/
onDataRequest: function (mode, keyword, onDataRequestCompleteCallback) {
},
/**
* Addition keyboard event handle for old and new input
* Why we need this:
* • Because some original js was binded to textarea, we need to bind it to contenteditable too.
* • Useful when you wanna passing some event trigger of old element to new editable content
* • Old input element already be trigger some event, then you need to pass some needed event to new editable element
* @param event
* @param oldInputEle
* @param newEditableEle
*/
onKeyPress: function (event, oldInputEle, newEditableEle) {
oldInputEle.trigger(event);
},
onKeyUp: function (event, oldInputEle, newEditableEle) {
oldInputEle.trigger(event);
},
onBlur: function (event, oldInputEle, newEditableEle) {
oldInputEle.trigger(event);
},
onPaste: function (event, oldInputEle, newEditableEle) {
oldInputEle.trigger(event);
},
onInput: function (oldInputEle, newEditableEle) {
},
// adjust popover relative position with its parent.
popoverOffset: {
x: -30,
y: 0
},
templates: {
container: '<div id="mentiony-container-[ID]" class="mentiony-container"></div>',
content: '<div id="mentiony-content-[ID]" class="mentiony-content" contenteditable="true"></div>',
popover: '<div id="mentiony-popover-[ID]" class="mentiony-popover"></div>',
list: '<ul id="mentiony-popover-[ID]" class="mentiony-list"></ul>',
listItem: '<li class="mentiony-item" data-item-id="">' +
'<div class="row">' +
'<div class="col-xs-3 col-sm-3 col-md-3 col-lg-3">' +
'<img src="https://avatars2.githubusercontent.com/u/1859127?v=3&s=140">' +
'</div>' +
'<div class="pl0 col-xs-9 col-sm-9 col-md-9 col-lg-9">' +
'<p class="title">Company name</p>' +
'<p class="help-block">Addition information</p>' +
'</div>' +
'</div>' +
'</li>',
normalText: '<span class="normal-text">&nbsp;</span>',
highlight: '<span class="highlight"></span>',
highlightContent: '<a href="[HREF]" data-item-id="[ITEM_ID]" class="mentiony-link">[TEXT]</a>',
}
};
if (typeof method === 'object' || !method) {
options = method;
}
var settings = $.extend({}, defaults, options);
return this.each(function () {
var instance = $.data(this, 'mentiony') || $.data(this, 'mentiony', new MentionsInput(settings));
if (typeof instance[method] === 'function') {
return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1));
} else if (typeof method === 'object' || !method) {
return instance.init.call(this, this);
} else {
$.error('Method ' + method + ' does not exist');
}
});
};
var MentionsInput = function (settings) {
var elmInputBoxContainer, elmInputBoxContent, elmInputBox,
elmInputBoxInitialWidth, elmInputBoxInitialHeight,
editableContentLineHeightPx,
popoverEle, list, elmInputBoxId,
elmInputBoxContentAbsPosition = {top: 0, left: 0},
dropDownShowing = false,
events = {
keyDown: false,
keyPress: false,
input: false,
keyup: false,
},
currentMention = {
keyword: '',
jqueryDomNode: null, // represent jQuery dom data
mentionItemDataSet: [], // list item json data was store here
lastActiveNode: 0,
charAtFound: false, // tracking @ char appear or not
}
;
var needMention = false; // Mention state
var inputId = Math.random().toString(36).substr(2, 6); // generate 6 character rand string
var onDataRequestCompleteCallback = function (responseData) {
populateDropdown(currentMention.keyword, responseData);
};
function initTextArea(ele) {
elmInputBox = $(ele);
if (elmInputBox.attr('data-mentions-input') == 'true') {
return;
} else {
elmInputBox.attr('data-mentions-input', 'true');
if (elmInputBox.attr('id').length == 0) {
elmInputBoxId = 'mentiony-input-' + inputId;
elmInputBox.attr('id', elmInputBoxId);
} else {
elmInputBoxId = elmInputBox.attr('id');
}
}
// Initial UI information
elmInputBoxInitialWidth = elmInputBox.prop('scrollWidth');
elmInputBoxInitialHeight = elmInputBox.prop('scrollHeight');
// Container
elmInputBoxContainer = $(settings.templates.container.replace('[ID]', inputId));
elmInputBoxContent = $(settings.templates.content.replace('[ID]', inputId));
// Make UI and hide the textarea
var placeholderText = elmInputBox.attr('placeholder');
if (typeof placeholderText === 'undefined') {
placeholderText = elmInputBox.text();
}
elmInputBoxContent.attr('data-placeholder', placeholderText);
elmInputBoxContainer.append(elmInputBox.clone().addClass('mention-input-hidden'));
elmInputBoxContainer.append(elmInputBoxContent);
elmInputBox.replaceWith(elmInputBoxContainer);
popoverEle = $(settings.templates.popover.replace('[ID]', inputId));
list = $(settings.templates.list.replace('[ID]', inputId));
elmInputBoxContainer.append(popoverEle);
popoverEle.append(list);
// Reset the input
elmInputBox = $('#' + elmInputBoxId);
// Update initial UI
var containerPadding = parseInt(elmInputBoxContainer.css('padding'));
if (settings.applyInitialSize) {
elmInputBoxContainer.addClass('initial-size');
elmInputBoxContainer.css({width: (elmInputBoxInitialWidth) + 'px'});
elmInputBoxContent.width((elmInputBoxInitialWidth - 2 * containerPadding) + 'px');
} else {
elmInputBoxContainer.addClass('auto-size');
}
elmInputBoxContent.css({minHeight: elmInputBoxInitialHeight + 'px'});
elmInputBoxContentAbsPosition = elmInputBoxContent.offset();
editableContentLineHeightPx = parseInt($(elmInputBoxContent.css('line-height')).selector);
// This event occured from top to down.
// When press a key: onInputBoxKeyDown --> onInputBoxKeyPress --> onInputBoxInput --> onInputBoxKeyUp
elmInputBoxContent.bind('keydown', onInputBoxKeyDown);
elmInputBoxContent.bind('keypress', onInputBoxKeyPress);
elmInputBoxContent.bind('input', onInputBoxInput);
elmInputBoxContent.bind('keyup', onInputBoxKeyUp);
elmInputBoxContent.bind('click', onInputBoxClick);
elmInputBoxContent.bind('blur', onInputBoxBlur);
elmInputBoxContent.bind('paste', onInputBoxPaste);
}
/**
* Put all special key handle here
* @param e
*/
function onInputBoxKeyDown(e) {
// log('onInputBoxKeyDown');
// reset events tracking
events = {
keyDown: true,
keyPress: false,
input: false,
keyup: false,
};
if (dropDownShowing) {
return handleUserChooseOption(e);
}
}
/**
* Character was entered was handled here
* This event occur when a printable was pressed. Or combined multi key was handle here, up/down can not read combined key
* NOTE: Delete key is not be triggered here
* @param e
*/
function onInputBoxKeyPress(e) {
// log('onInputBoxKeyPress');
events.keyPress = true;
if (!needMention) {
// Try to check if need mention
needMention = (e.keyCode === KEY.AT || e.which === KEY.AT);
// log(needMention, 'needMention', 'info');
}
// force focus on element so it triggers on IE
content.click();
settings.onKeyPress.call(this, e, elmInputBox, elmInputBoxContent);
}
/**
* When input value was change, with any key effect the input value
* Delete was trigger here
*/
function onInputBoxInput(e) {
// log('onInputBoxInput');
events.input = true;
// convert android character to codes so it could be matched
var converted = e.originalEvent.data.charCodeAt(0);
// trigger mentiony on mobile devices
if (!needMention) {
needMention = ((e.keyCode === KEY.AT || e.which === KEY.AT) || (converted === KEY.AT));
}
// force focus on element so it triggers on IE
content.click();
settings.onInput.call(this, elmInputBox, elmInputBoxContent);
}
/**
* Put all special key handle here
* @param e
*/
function onInputBoxKeyUp(e) {
// log('onInputBoxKeyUp');
events.keyup = true;
// log(events, 'events');
if (events.input) {
updateDataInputData(e);
}
if (needMention) {
// Update mention keyword only inputing(not enter), left, right
if ((e.keyCode !== KEY.RETURN || e.which === KEY.RETURN) && (events.input || (e.keyCode === KEY.LEFT || e.which === KEY.LEFT) || (e.keyCode === KEY.RIGHT || e.which === KEY.RIGHT))) {
updateMentionKeyword(e);
doSearchAndShow();
}
}
settings.onKeyUp.call(this, e, elmInputBox, elmInputBoxContent);
}
function onInputBoxClick(e) {
// log('onInputBoxClick');
if (needMention) {
updateMentionKeyword(e);
doSearchAndShow();
}
}
function onInputBoxBlur(e) {
// log('onInputBoxBlur');
settings.onBlur.call(this, e, elmInputBox, elmInputBoxContent);
}
function onInputBoxPaste(e) {
// log('onInputBoxPaste');
settings.onPaste.call(this, e, elmInputBox, elmInputBoxContent);
}
function onListItemClick(e) {
//$(this) is the clicked listItem
setSelectedMention($(this));
choseMentionOptions(true);
}
/**
* Save content to original textarea element, using for form submit to server-side
* @param e
*/
function updateDataInputData(e) {
var elmInputBoxText = elmInputBoxContent.html();
elmInputBox.val(convertSpace(elmInputBoxText));
log(elmInputBox.val(), 'elmInputBoxText : ');
tmpEle = elmInputBox;
}
/**
* Trim space and &nbsp; from string
* @param text
* @returns {*|void|string|XML}
*/
function trimSpace(text) {
return text.replace(/^(&nbsp;|&nbsp|\s)+|(&nbsp;|&nbsp|\s)+$/g, '');
}
/**
* Convert &nbsp; to space
* @param text
* @returns {*|void|string|XML}
*/
function convertSpace(text) {
return text.replace(/(&nbsp;)+/g, ' ');
}
/**
* Handle User search action with timeout, and show mention if needed
*/
function doSearchAndShow() {
if (settings.timeOut > 0) {
if (settings.globalTimeout !== null) {
clearTimeout(settings.globalTimeout);
}
settings.globalTimeout = setTimeout(function () {
settings.globalTimeout = null;
settings.onDataRequest.call(this, 'search', currentMention.keyword, onDataRequestCompleteCallback);
}, settings.timeOut);
} else {
settings.onDataRequest.call(this, 'search', currentMention.keyword, onDataRequestCompleteCallback);
}
}
/**
* Build and handle dropdown popover content show/hide
* @param keyword
* @param responseData
*/
function populateDropdown(keyword, responseData) {
list.empty();
currentMention.jqueryDomNode = null;
currentMention.mentionItemDataSet = responseData;
if (responseData.length) {
if (currentMention.charAtFound === true) {
showDropDown();
}
responseData.forEach(function (item, index) {
var listItem = $(settings.templates.listItem);
listItem.attr('data-item-id', item.id);
listItem.find('img:first').attr('src', item.avatar);
listItem.find('p.title:first').html(item.name);
listItem.find('p.help-block:first').html(item.info);
listItem.bind('click', onListItemClick);
list.append(listItem);
});
} else {
hideDropDown();
}
}
/**
* Show popover box contain result item
*/
function showDropDown() {
var curAbsPos = getSelectionCoords();
dropDownShowing = true;
popoverEle.css({
display: 'block',
top: curAbsPos.y - (elmInputBoxContentAbsPosition.top - $(document).scrollTop()) + (editableContentLineHeightPx + 10),
left: curAbsPos.x - elmInputBoxContentAbsPosition.left
});
}
function hideDropDown() {
dropDownShowing = false;
popoverEle.css({display: 'none'});
}
/**
* Handle the event when user choosing / chose.
* @param e
* @returns {boolean} Continue to run the rest of code or not. If choosing metion or choosed mention, will stop doing anything else.
*/
function handleUserChooseOption(e) {
if (!dropDownShowing) {
return true;
}
if ((e.keyCode === KEY.UP || e.which === KEY.UP) || (e.keyCode === KEY.DOWN || e.which === KEY.DOWN)) {
choosingMentionOptions(e);
return false;
}
// Try to exit mention state: Stop mention if @, Home, Enter, Tabs
if (((e.keyCode === KEY.HOME || e.which === KEY.HOME))
|| ((e.keyCode === KEY.RETURN || e.which === KEY.RETURN))
|| ((e.keyCode === KEY.TAB || e.which === KEY.TAB))
) {
choseMentionOptions();
return false;
}
return true;
}
/**
* Update mention keyword on: Input / LEFT-RIGHT
*/
function updateMentionKeyword(e) {
if (document.selection) {
// var node = document.selection.createRange().parentElement(); // IE
var node = document.selection.createRange(); // IE
// TODO: Test on IE
} else {
// var node = window.getSelection().anchorNode.parentNode; // everyone else
var node = window.getSelection().anchorNode; // everyone else
}
var textNodeData = node.data;
if (typeof textNodeData === 'undefined') {
textNodeData = '';
}
var cursorPosition = getSelectionEndPositionInCurrentLine(); // passing the js DOM ele
// Save current position for mouse click handling, because when you use mouse, no selection was found.
currentMention.lastActiveNode = node;
// reset and set new mention keyword
currentMention.keyword = '';
var i = cursorPosition - 1; // NOTE: cursorPosition is Non-zero base
var next = true;
while (next) {
var charAt = textNodeData.charAt(i);
if (charAt === '' || charAt === settings.triggerChar) {
next = false;
}
i--;
}
currentMention.keyword = textNodeData.substring(i + 1, cursorPosition);
if (currentMention.keyword.indexOf(settings.triggerChar) === -1) {
currentMention.keyword = '';
currentMention.charAtFound = false;
// NOTE: Still need mention but turn off dropdown now
hideDropDown();
} else {
currentMention.keyword = currentMention.keyword.substring(1, cursorPosition);
currentMention.charAtFound = true;
}
log(currentMention.keyword, 'currentMention.keyword');
}
function getMentionKeyword() {
return currentMention.keyword;
}
/**
* Update UI, show the selected item
* @param item Jquery object represent the list-item
*/
function setSelectedMention(item) {
currentMention.jqueryDomNode = item;
updateSelectedMentionUI(item);
log(item, 'setSelectedMention item: ');
}
function updateSelectedMentionUI(selectedMentionItem) {
$.each(list.children(), function (i, listItem) {
$(listItem).removeClass('active');
});
selectedMentionItem.addClass('active');
}
/**
* Handle when user use keyboard to choose mention
* @param e
*/
function choosingMentionOptions(e) {
log('choosingMentionOptions');
// Get Selected mention Item
if (currentMention.jqueryDomNode === null) {
setSelectedMention(list.children().first());
}
var item = [];
if (e.keyCode === KEY.DOWN || e.which === KEY.DOWN) {
item = currentMention.jqueryDomNode.next();
} else if (e.keyCode === KEY.UP || e.which === KEY.UP) {
item = currentMention.jqueryDomNode.prev();
}
if (item.length === 0) {
item = currentMention.jqueryDomNode;
}
setSelectedMention(item);
}
/**
* Handle UI and data when user chose an mention option.
*/
function choseMentionOptions(chooseByMouse) {
if (chooseByMouse === 'undefined') {
chooseByMouse = false;
}
log('choosedMentionOptions by ' + (chooseByMouse ? 'Mouse' : 'Keyboard'));
var currentMentionItemData = {};
var selectedId = currentMention.jqueryDomNode.attr('data-item-id');
for (var i = 0, len = currentMention.mentionItemDataSet.length; i < len; i++) {
if (selectedId == currentMention.mentionItemDataSet[i].id) {
currentMentionItemData = currentMention.mentionItemDataSet[i];
break;
}
}
var highlightNode = $(settings.templates.highlight);
var highlightContentNode = $(settings.templates.highlightContent
.replace('[HREF]', currentMentionItemData.href)
.replace('[TEXT]', currentMentionItemData.name)
.replace('[ITEM_ID]', currentMentionItemData.id)
);
highlightNode.append(highlightContentNode);
replaceTextInRange('@' + currentMention.keyword, highlightNode.prop('outerHTML'), chooseByMouse);
// Finish mention
log('Finish mention', '', 'warn');
needMention = false; // Reset mention state
currentMention.keyword = ''; // reset current Data if start with @
hideDropDown();
updateDataInputData();
}
function log(msg, prefix, level) {
if (typeof level === 'undefined') {
level = 'log';
}
if (settings.debug === 1) {
eval("console." + level + "(inputId, prefix ? prefix + ':' : '', msg);");
}
}
/**
* Intend to replace current @key-word with mention structured HTML
* NOTE: depend on jQuery
* @param fromString
* @param toTextHtml
* @param choosedByMouse
*/
function replaceTextInRange(fromString, toTextHtml, choosedByMouse) {
var positionInfo = {
startBefore: 0,
startAfter: 0,
stopBefore: 0,
stopAfter: 0,
};
var sel = window.getSelection();
var range;
// Move caret to current caret in case of contentediable is not active --> no caret
if (choosedByMouse !== 'undefined' && choosedByMouse === true) {
var lastActiveNode = currentMention.lastActiveNode;
try {
var offset = lastActiveNode.data.length;
} catch (e) {
log(e, 'lastActiveNode Error:');
var offset = 0;
}
range = document.createRange();
range.setStart(lastActiveNode, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
// TODO: Bug: Choose mention by mouse: @123456: IF user put caret between 2 & 3 THEN the highlight will replace only 3456
}
var isIE = false;
var stopPos = sel.focusOffset;
var startPos = stopPos - fromString.length;
if (window.getSelection) {
isIE = false;
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
isIE = true;
}
if (startPos !== stopPos) {
// replace / Remove content
range.setStart(sel.anchorNode, startPos);
range.setEnd(sel.anchorNode, stopPos);
range.deleteContents();
}
// insert
var node = document.createElement('span');
node.setAttribute('class', 'mention-area');
node.innerHTML = toTextHtml;
range.insertNode(node);
range.setEnd(sel.focusNode, range.endContainer.length);
positionInfo.startBefore = startPos;
positionInfo.stopBefore = stopPos;
positionInfo.startAfter = startPos;
positionInfo.stopAfter = startPos + node.innerText.length;
// move cursor to end of keyword after replace
var stop = false;
node = $(sel.anchorNode);
while (!stop) {
if (node.next().text().length === 0) {
stop = true;
} else {
node = node.next();
}
}
// insert <newElem> after list
var newElem = $(settings.templates.normalText).insertAfter(node);
// move caret to after <newElem>
range = document.createRange();
range.setStartAfter(newElem.get(0));
range.setEndAfter(newElem.get(0));
sel.removeAllRanges();
sel.addRange(range);
return positionInfo;
}
// Public methods
return {
init: function (domTarget) {
initTextArea(domTarget);
},
};
};
/**
* Get current caret / selection absolute position (relative to viewport , not to parent)
* Thank to Tim Down: http://stackoverflow.com/questions/6846230/coordinates-of-selected-text-in-browser-page
* NOTE: This is relative to viewport, not its parent
* @param win
* @returns {{x: number, y: number}}
*/
function getSelectionCoords(win) {
win = win || window;
var doc = win.document;
var sel = doc.selection, range, rects, rect;
var x = 0, y = 0;
if (sel) {
if (sel.type != "Control") {
range = sel.createRange();
range.collapse(true);
x = range.boundingLeft;
y = range.boundingTop;
}
} else if (win.getSelection) {
sel = win.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0).cloneRange();
if (range.getClientRects) {
range.collapse(true);
rects = range.getClientRects();
if (rects.length > 0) {
rect = rects[0];
}
x = rect.left;
y = rect.top;
}
// Fall back to inserting a temporary element
if (x == 0 && y == 0) {
var span = doc.createElement("span");
if (span.getClientRects) {
// Ensure span has dimensions and position by
// adding a zero-width space character
span.appendChild(doc.createTextNode("\u200b"));
range.insertNode(span);
rect = span.getClientRects()[0];
x = rect.left;
y = rect.top;
var spanParent = span.parentNode;
spanParent.removeChild(span);
// Glue any broken text nodes back together
spanParent.normalize();
}
}
}
}
return {x: x, y: y};
}
/**
* Get current caret position in current line (not the current contenteditable)
* @returns {number}
*/
function getSelectionEndPositionInCurrentLine() {
var selectionEndPos = 0;
if (window.getSelection) {
var sel = window.getSelection();
selectionEndPos = sel.focusOffset;
}
return selectionEndPos;
}
// TODO: Add setting option: showMentionedItem :Filter mentioned item, we should not show a item if it already mentioned
// TODO: Elastic search in case of local data (in case of ajax --> depend on server-side)
// TODO: Use higlight
// TODO: Dropdown: scroll to see more item WHEN user press DOWN and reach the end of list.
// TODO: Change to A cross-browser JavaScript range and selection library.: https://github.com/timdown/rangy
}(jQuery));

View File

@@ -0,0 +1,306 @@
// SmoothScroll v0.9.9
// Licensed under the terms of the MIT license.
// People involved
// - Balazs Galambosi: maintainer (CHANGELOG.txt)
// - Patrick Brunner (patrickb1991@gmail.com)
// - Michael Herf: ssc_pulse Algorithm
function ssc_init() {
if (!document.body) return;
var e = document.body;
var t = document.documentElement;
var n = window.innerHeight;
var r = e.scrollHeight;
ssc_root = document.compatMode.indexOf("CSS") >= 0 ? t : e;
ssc_activeElement = e;
ssc_initdone = true;
if (top != self) {
ssc_frame = true
} else if (r > n && (e.offsetHeight <= n || t.offsetHeight <= n)) {
ssc_root.style.height = "auto";
if (ssc_root.offsetHeight <= n) {
var i = document.createElement("div");
i.style.clear = "both";
e.appendChild(i)
}
}
if (!ssc_fixedback) {
e.style.backgroundAttachment = "scroll";
t.style.backgroundAttachment = "scroll"
}
if (ssc_keyboardsupport) {
ssc_addEvent("keydown", ssc_keydown)
}
}
function ssc_scrollArray(e, t, n, r) {
r || (r = 1e3);
ssc_directionCheck(t, n);
ssc_que.push({
x: t,
y: n,
lastX: t < 0 ? .99 : -.99,
lastY: n < 0 ? .99 : -.99,
start: +(new Date)
});
if (ssc_pending) {
return
}
var i = function () {
var s = +(new Date);
var o = 0;
var u = 0;
for (var a = 0; a < ssc_que.length; a++) {
var f = ssc_que[a];
var l = s - f.start;
var c = l >= ssc_animtime;
var h = c ? 1 : l / ssc_animtime;
if (ssc_pulseAlgorithm) {
h = ssc_pulse(h)
}
var p = f.x * h - f.lastX >> 0;
var d = f.y * h - f.lastY >> 0;
o += p;
u += d;
f.lastX += p;
f.lastY += d;
if (c) {
ssc_que.splice(a, 1);
a--
}
}
if (t) {
var v = e.scrollLeft;
e.scrollLeft += o;
if (o && e.scrollLeft === v) {
t = 0
}
}
if (n) {
var m = e.scrollTop;
e.scrollTop += u;
if (u && e.scrollTop === m) {
n = 0
}
}
if (!t && !n) {
ssc_que = []
}
if (ssc_que.length) {
setTimeout(i, r / ssc_framerate + 1)
} else {
ssc_pending = false
}
};
setTimeout(i, 0);
ssc_pending = true
}
function ssc_wheel(e) {
if (!ssc_initdone) {
ssc_init()
}
var t = e.target;
var n = ssc_overflowingAncestor(t);
if (!n || e.defaultPrevented || ssc_isNodeName(ssc_activeElement, "embed") || ssc_isNodeName(t, "embed") && /\.pdf/i.test(t.src)) {
return true
}
var r = e.wheelDeltaX || 0;
var i = e.wheelDeltaY || 0;
if (!r && !i) {
i = e.wheelDelta || 0
}
if (Math.abs(r) > 1.2) {
r *= ssc_stepsize / 120
}
if (Math.abs(i) > 1.2) {
i *= ssc_stepsize / 120
}
ssc_scrollArray(n, -r, -i);
e.preventDefault()
}
function ssc_keydown(e) {
var t = e.target;
var n = e.ctrlKey || e.altKey || e.metaKey;
if (/input|textarea|embed/i.test(t.nodeName) || t.isContentEditable || e.defaultPrevented || n) {
return true
}
if (ssc_isNodeName(t, "button") && e.keyCode === ssc_key.spacebar) {
return true
}
var r, i = 0,
s = 0;
var o = ssc_overflowingAncestor(ssc_activeElement);
if (o) {
var u = o.clientHeight;
}
if (o == document.body) {
u = window.innerHeight
}
switch (e.keyCode) {
case ssc_key.up:
s = -ssc_arrowscroll;
break;
case ssc_key.down:
s = ssc_arrowscroll;
break;
case ssc_key.spacebar:
r = e.shiftKey ? 1 : -1;
s = -r * u * .9;
break;
case ssc_key.pageup:
s = -u * .9;
break;
case ssc_key.pagedown:
s = u * .9;
break;
case ssc_key.home:
s = -o.scrollTop;
break;
case ssc_key.end:
var a = o.scrollHeight - o.scrollTop - u;
s = a > 0 ? a + 10 : 0;
break;
case ssc_key.left:
i = -ssc_arrowscroll;
break;
case ssc_key.right:
i = ssc_arrowscroll;
break;
default:
return true
}
ssc_scrollArray(o, i, s);
e.preventDefault()
}
function ssc_mousedown(e) {
ssc_activeElement = e.target
}
function ssc_setCache(e, t) {
for (var n = e.length; n--;) ssc_cache[ssc_uniqueID(e[n])] = t;
return t
}
function ssc_overflowingAncestor(e) {
var t = [];
var n = ssc_root.scrollHeight;
do {
var r = ssc_cache[ssc_uniqueID(e)];
if (r) {
return ssc_setCache(t, r)
}
t.push(e);
if (n === e.scrollHeight) {
if (!ssc_frame || ssc_root.clientHeight + 10 < n) {
return ssc_setCache(t, document.body)
}
} else if (e.clientHeight + 10 < e.scrollHeight) {
overflow = getComputedStyle(e, "").getPropertyValue("overflow");
if (overflow === "scroll" || overflow === "auto") {
return ssc_setCache(t, e)
}
}
} while (e = e.parentNode)
}
function ssc_addEvent(e, t, n) {
window.addEventListener(e, t, n || false)
}
function ssc_removeEvent(e, t, n) {
window.removeEventListener(e, t, n || false)
}
function ssc_isNodeName(e, t) {
return e.nodeName.toLowerCase() === t.toLowerCase()
}
function ssc_directionCheck(e, t) {
e = e > 0 ? 1 : -1;
t = t > 0 ? 1 : -1;
if (ssc_direction.x !== e || ssc_direction.y !== t) {
ssc_direction.x = e;
ssc_direction.y = t;
ssc_que = []
}
}
function ssc_pulse_(e) {
var t, n, r;
e = e * ssc_pulseScale;
if (e < 1) {
t = e - (1 - Math.exp(-e))
} else {
n = Math.exp(-1);
e -= 1;
r = 1 - Math.exp(-e);
t = n + r * (1 - n)
}
return t * ssc_pulseNormalize
}
function ssc_pulse(e) {
if (e >= 1) return 1;
if (e <= 0) return 0;
if (ssc_pulseNormalize == 1) {
ssc_pulseNormalize /= ssc_pulse_(1)
}
return ssc_pulse_(e)
}
var ssc_framerate = 150;
var ssc_animtime = 500;
var ssc_stepsize = 150;
var ssc_pulseAlgorithm = true;
var ssc_pulseScale = 6;
var ssc_pulseNormalize = 1;
var ssc_keyboardsupport = true;
var ssc_arrowscroll = 50;
var ssc_frame = false;
var ssc_direction = {
x: 0,
y: 0
};
var ssc_initdone = false;
var ssc_fixedback = true;
var ssc_root = document.documentElement;
var ssc_activeElement;
var ssc_key = {
left: 37,
up: 38,
right: 39,
down: 40,
spacebar: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36
};
var ssc_que = [];
var ssc_pending = false;
var ssc_cache = {};
setInterval(function () {
ssc_cache = {}
}, 10 * 1e3);
var ssc_uniqueID = function () {
var e = 0;
return function (t) {
return t.ssc_uniqueID || (t.ssc_uniqueID = e++)
}
}();
var ischrome = /chrome/.test(navigator.userAgent.toLowerCase());
if (ischrome) {
ssc_addEvent("mousedown", ssc_mousedown);
ssc_addEvent("mousewheel", ssc_wheel);
ssc_addEvent("load", ssc_init)
}

View File

@@ -0,0 +1,41 @@
:root {
--ant-primary-color: #1890ff;
--ant-primary-color-hover: #40a9ff;
--ant-primary-color-active: #096dd9;
--ant-primary-color-outline: rgba(24, 144, 255, .2);
--ant-primary-1: #e6f7ff;
--ant-primary-2: #bae7ff;
--ant-primary-3: #91d5ff;
--ant-primary-4: #69c0ff;
--ant-primary-5: #40a9ff;
--ant-primary-6: #1890ff;
--ant-primary-7: #096dd9;
--ant-primary-color-deprecated-l-35: #cbe6ff;
--ant-primary-color-deprecated-l-20: #7ec1ff;
--ant-primary-color-deprecated-t-20: #46a6ff;
--ant-primary-color-deprecated-t-50: #8cc8ff;
--ant-primary-color-deprecated-f-12: rgba(24, 144, 255, .12);
--ant-primary-color-active-deprecated-f-30: rgba(230, 247, 255, .3);
--ant-primary-color-active-deprecated-d-02: #dcf4ff;
--ant-success-color: #52c41a;
--ant-success-color-hover: #73d13d;
--ant-success-color-active: #389e0d;
--ant-success-color-outline: rgba(82, 196, 26, .2);
--ant-success-color-deprecated-bg: #f6ffed;
--ant-success-color-deprecated-border: #b7eb8f;
--ant-error-color: #ff4d4f;
--ant-error-color-hover: #ff7875;
--ant-error-color-active: #d9363e;
--ant-error-color-outline: rgba(255, 77, 79, .2);
--ant-error-color-deprecated-bg: #fff2f0;
--ant-error-color-deprecated-border: #ffccc7;
--ant-warning-color: #faad14;
--ant-warning-color-hover: #ffc53d;
--ant-warning-color-active: #d48806;
--ant-warning-color-outline: rgba(250, 173, 20, .2);
--ant-warning-color-deprecated-bg: #fffbe6;
--ant-warning-color-deprecated-border: #ffe58f;
--ant-info-color: #1890ff;
--ant-info-color-deprecated-bg: #e6f7ff;
--ant-info-color-deprecated-border: #91d5ff
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,407 @@
$red: #ff6252;
$column_count: var(--column_count);
$column_width: var(--column_width);
:root {
--column_count: 20;
--column_width: 70px;
}
.header {
color: #202125;
margin-bottom: 40px;
h2 {
font-weight: 600;
}
p {
font-weight: 300;
}
}
.wrapper {
max-width: 100vw;
min-width: 700px;
margin: 0 auto;
padding: 0px;
gap: 10px;
overflow: auto;
max-height: 80vh;
}
.gantt {
display: grid;
border: 0;
position: relative;
box-sizing: border-box;
margin-bottom: 10px;
&__row {
display: grid;
grid-template-columns: 200px 1fr;
background-color: #fff;
&--empty {
background-color: #fafafa !important;
z-index: 1;
.gantt__row-first {
border-width: 1px 1px 0 0;
border-left: 1px solid #f0f0f0;
}
}
&--lines {
position: absolute;
height: 100%;
width: 100%;
background-color: transparent;
grid-template-columns: 200px repeat($column_count, $column_width);
span {
display: block;
border-right: 1px solid #f0f0f0;
&.weekend {
background: linear-gradient(-45deg, rgb(230, 230, 230) 12.5%, transparent 12.5%, transparent 50%, rgb(230, 230, 230) 50%, rgb(230, 230, 230) 62.5%, transparent 62.5%, transparent) 0% 0% / 5px 5px;
z-index: 0;
}
}
&:after {
grid-row: 1;
grid-column: 0;
background-color: #1688b345;
z-index: 2;
height: 100%;
}
}
&--months {
color: #000;
background-color: #fafafa !important;
grid-template-columns: 200px repeat($column_count, $column_width);
.gantt__row-first {
border-top: 0 !important;
background-color: #fafafa !important;
}
span {
text-align: center;
font-size: 13px;
align-self: center;
font-weight: bold;
padding: 20px 0;
}
}
&-first {
background-color: #fff;
border: none;
padding: 15px 10px;
font-size: 13px;
// font-weight: bold;
text-align: left;
}
&-first-user {
border-width: 1px 1px 0px 0px;
}
&-bars {
list-style: none;
display: grid;
padding: 0px 0px;
margin: 0px 0px 7px 0px;
grid-template-columns: repeat($column_count, $column_width);
grid-gap: 0px 0;
align-items: center;
li {
font-weight: 500;
text-align: left;
font-size: 13px;
min-height: 15px;
background-color: #55de84;
padding: 0px 0px;
margin: 7px 1px 0px 1px;
color: #fff;
overflow: hidden;
position: relative;
cursor: pointer;
line-height: 21px;
.ant-btn {
width: 100%;
text-align: left;
padding: 0px 15px;
color: white;
border: none;
font-weight: 400;
:hover {
color: white;
}
}
&.stripes {
background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(255, 255, 255, .1) 5px, rgba(255, 255, 255, .1) 12px);
}
&:before,
&:after {
content: "";
height: 100%;
top: 0;
z-index: 4;
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
}
&:before {
left: 0;
}
&:after {
right: 0;
}
}
}
&-month-range {
list-style: none;
display: grid;
padding: 12px 0px;
margin: 0;
grid-template-columns: repeat($column_count, $column_width);
grid-gap: 8px 0;
border-top: 1px solid #f0f0f0;
align-items: center;
li {
font-weight: 500;
text-align: left;
font-size: 14px;
min-height: 15px;
background-color: #55de84;
padding: 0px 0px;
margin: 0px 1px;
color: #fff;
overflow: hidden;
position: relative;
animation: 0.5s ease-out 0s 1 scale-up-hor-left;
.ant-btn {
width: 100%;
text-align: left;
padding: 0px 8px;
color: white;
border: none;
font-weight: 400;
:hover {
color: white;
}
}
&:before,
&:after {
content: "";
height: 100%;
top: 0;
z-index: 4;
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
}
&:before {
left: 0;
}
&:after {
right: 0;
}
}
}
}
}
.project-header {
background-color: #f0f2f5 !important;
font-size: 14px;
border-right: 1px solid #f0f0f0;
border-top: 1px solid #bdbdbd;
margin-top: 1px;
}
.col {
padding: 16px 0;
text-align: center;
border-radius: 0;
min-height: 30px;
margin-top: 8px;
margin-bottom: 8px;
background: rgba(0, 160, 233, 0.7);
color: #fff;
}
.col.right {
background: #00a0e9;
}
.sticky-left {
position: sticky;
left: 0;
z-index: 1;
}
.grid-header {
position: sticky;
top: 0;
z-index: 1;
}
.grid-header.sticky-left {
z-index: 2;
}
.grid-column {
height: 100%;
}
.gantt__row {
a {
color: rgba(0, 0, 0, 0.85);
font-weight: 600;
}
}
.gantt__row--months span {
font-weight: 500;
padding: 12px 10px;
color: rgba(0, 0, 0, 0.85);
text-align: left;
}
.gantt-user {
border-right: 1px solid #f0f0f0;
border-left: 1px solid #f0f0f0;
padding-left: 20px;
}
.dotted-border {
border-bottom: 1px dashed #707070;
:last-of-type {
border-bottom: none !important;
}
}
.p-default {
padding: 24px !important;
}
.border-l-b {
border-left: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
border-right: 1px solid #f0f0f0;
}
.project-name {
font-size: 14px;
font-weight: 500;
color: #292929;
}
.project-name:hover {
color: #40a9ff;
}
.ant-popover-inner-content {
padding: 12px 24px;
}
/** ---------- animations ---------- */
@-webkit-keyframes scale-up-hor-left {
0% {
-webkit-transform: scaleX(0.4);
transform: scaleX(0.4);
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
100% {
-webkit-transform: scaleX(1);
transform: scaleX(1);
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
}
@keyframes scale-up-hor-left {
0% {
-webkit-transform: scaleX(0.4);
transform: scaleX(0.4);
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
100% {
-webkit-transform: scaleX(1);
transform: scaleX(1);
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
}
@-webkit-keyframes slideDown {
from {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
opacity: 0;
}
to {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideDown {
from {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
opacity: 0;
}
to {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
.weekend {
background: linear-gradient(-45deg, rgb(230, 230, 230) 12.5%, transparent 12.5%, transparent 50%, rgb(230, 230, 230) 50%, rgb(230, 230, 230) 62.5%, transparent 62.5%, transparent) 0% 0% / 5px 5px;
}
.sunday {
border-right: solid 1px silver !important;
}
.member {
font-size: 13px;
font-weight: 400;
}
.header-bg {
background-color: #fafafa !important;
}
.month {
font-weight: 500;
}