diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 17948614b..cd6a65853 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -277,7 +277,7 @@ margin-bottom: 15px; } - button { + button:not('.pure-button') { display: inline-block; position: relative; margin: 6px 8px; @@ -293,6 +293,9 @@ &:not(:first-child) { margin-left: @alertify_padding-base !important; } + &.left { + float: left; + } } } } diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index a79717550..841f47679 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -53,6 +53,49 @@ } } +.markdown_cryptpad() { + word-wrap: break-word; + + h1, h2, h3, h4, h5, h6 { + font-weight: bold; + padding-bottom: 0.3em; + border-bottom: 1px solid #eee; + } + li { + min-height: 22px; + } + .todo-list-item { + list-style: none; + position: relative; + .fa { + position: absolute; + margin-left: -17px; + margin-top: 4px; + &.fa-check-square { + font-size: 15px; + margin-top: 5px; + } + } + } + media-tag { + * { + max-width: 100%; + } + iframe[src$=".pdf"] { + width: 100%; + height: 80vh; + max-height: 90vh; + } + } + media-tag:empty { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid #BBB; + } + +} + .markdown_preformatted-code (@color: #333) { pre > code { display: block; diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less index faa302b0a..4445224e7 100644 --- a/customize.dist/src/less2/include/tokenfield.less +++ b/customize.dist/src/less2/include/tokenfield.less @@ -11,7 +11,6 @@ .tools_unselectable(); display: flex; flex-wrap: wrap; - justify-content: space-around; height: auto; min-height: 34px; padding-bottom: 0px; @@ -29,6 +28,7 @@ background-color: #ededed; white-space: nowrap; margin: 2px 0; + margin-right: 5px; height: 24px; vertical-align: middle; cursor: default; diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 5efb69068..3c59218f4 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -168,6 +168,7 @@ position: relative; order: -2; resize: horizontal; + z-index: 1; #cp-app-contacts-container { height: 100%; } @@ -196,6 +197,7 @@ padding: 10px; box-sizing: border-box; order: -3; + z-index: 1; .cp-toolbar-userlist-drawer-close { position: absolute; margin-top: -10px; diff --git a/www/code/app-code.less b/www/code/app-code.less index 219143a34..39d7db27a 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -65,47 +65,12 @@ box-sizing: border-box; //font-family: Calibri,Ubuntu,sans-serif; font: @colortheme_app-font; - word-wrap: break-word; position: relative; flex: 1; - h1, h2, h3, h4, h5, h6 { - font-weight: bold; - padding-bottom: 0.3em; - border-bottom: 1px solid #eee; - } - li { - min-height: 22px; - } - .todo-list-item { - list-style: none; - .fa { - position: absolute; - margin-left: -17px; - margin-top: 4px; - &.fa-check-square { - font-size: 15px; - margin-top: 5px; - } - } - } - media-tag { - * { - max-width:100%; - } - iframe[src$=".pdf"] { - width: 100%; - height: 80vh; - max-height: 90vh; - } - } - media-tag:empty { - width: 100px; - height: 100px; - display: inline-block; - border: 1px solid #BBB; - } .markdown_main(); + .markdown_cryptpad(); + .cp-app-code-preview-empty { display: none; } diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 8cb1b7c2e..f86c55e01 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -64,7 +64,7 @@ define([ editor._noCursorUpdate = false; editor.scrollTo(scroll.left, scroll.top); - if (!editor.state.focused) { return; } + if (!editor.hasFocus()) { return; } if(selects[0] === selects[1]) { editor.setCursor(posToCursor(selects[0], remoteDoc)); diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less index 248c66c1d..f84c7c49b 100644 --- a/www/kanban/app-kanban.less +++ b/www/kanban/app-kanban.less @@ -1,6 +1,8 @@ @import (reference) "../../customize/src/less2/include/browser.less"; @import (reference) "../../customize/src/less2/include/framework.less"; @import (reference) "../../customize/src/less2/include/tools.less"; +@import (reference) "../../customize/src/less2/include/markdown.less"; +@import (reference) "../../customize/src/less2/include/avatar.less"; // body &.cp-app-kanban { @@ -14,11 +16,142 @@ flex-flow: column; max-height: 100%; min-height: auto; + color: @cryptpad_text_col; + + @board-bg: #eaeaea; + + @palette0: #888; // Default bg color for header + @palette1: #FFD4D4; + @palette2: #FFDECA; + @palette3: #FFE69C; + @palette4: #DBFFB7; + @palette5: #AFFDC2; + @palette6: #C9FFFE; + @palette7: #C8D6FF; + @palette8: #E4CAFF; + + .kanban-board-header { + background-color: #c9c9c9; + } + .kanban-board { + .kanban-board-inner { + background-color: @board-bg; + max-height: 100%; + display: flex; + flex-flow: column; + } + color: @cryptpad_text_col; + } + + .cp-kanban-palette-nocolor { + background-color: @palette0; + } + .cp-kanban-palette-color1 { + background-color: @palette1; + &.kanban-board-inner { + background-color: lighten(@palette1, 5%); + } + } + .cp-kanban-palette-color2 { + background-color: @palette2; + &.kanban-board-inner { + background-color: lighten(@palette2, 5%); + } + } + .cp-kanban-palette-color3 { + background-color: @palette3; + &.kanban-board-inner { + background-color: lighten(@palette3, 10%); + } + } + .cp-kanban-palette-color4 { + background-color: @palette4; + &.kanban-board-inner { + background-color: lighten(@palette4, 10%); + } + } + .cp-kanban-palette-color5 { + background-color: @palette5; + &.kanban-board-inner { + background-color: lighten(@palette5, 10%); + } + } + .cp-kanban-palette-color6 { + background-color: @palette6; + &.kanban-board-inner { + background-color: lighten(@palette6, 5%); + } + } + .cp-kanban-palette-color7 { + background-color: @palette7; + &.kanban-board-inner { + background-color: lighten(@palette7, 5%); + } + } + .cp-kanban-palette-color8 { + background-color: @palette8; + &.kanban-board-inner { + background-color: lighten(@palette8, 5%); + } + } + + .cp-kanban-edit-modal { + display: flex; + flex-flow: column; + overflow: hidden; + } + #cp-kanban-edit-conflicts { + padding: 5px; + background: #eee; + color: @cryptpad_text_col; + font-size: 14px; + .cp-kanban-cursors { + margin-top: 5px; + } + margin-bottom: 5px; + } + #cp-kanban-edit-body { + border: 1px solid @colortheme_modal-input; + .CodeMirror { + height: 105px; + } + .CodeMirror-scroll { + box-sizing: content-box; + } + .cp-markdown-toolbar { + background-color: #eee; + color: @cryptpad_text_col; + } + margin-bottom: 15px; + } + #cp-kanban-edit-colors { + display: flex; + justify-content: space-between; + .cp-kanban-palette { + display: inline-block; + border-radius: 50%; + height: 30px; + width: 30px; + text-align: center; + line-height: 30px; + color: @cryptpad_text_col; + } + .cp-kanban-palette-nocolor { + border: 1px solid @cryptpad_text_col; + } + } + #cp-kanban-edit-tags { + .tokenfield { + margin: 0; + } + margin-bottom: 15px; + } #cp-app-kanban-container { flex: 1; display: flex; flex-flow: column; + overflow-x: hidden; } #cp-app-kanban-editor { flex: 1; @@ -27,58 +160,272 @@ height: 100%; overflow: hidden; } - #cp-app-kanban-content { - flex: 1; - overflow-y: auto; + + .kanban-edit-item { + padding: 5px; + } + + .cp-kanban-cursors { + &:empty { display: none; } + order: 2; + width: 100%; + &> span { + display: inline-block; + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + margin-right: 5px; + .tools_unselectable(); + cursor: default; + } + } + .kanban-item { display: flex; - flex-flow: column; - .kanban-container-outer { - flex: 1; + align-items: center; + justify-content: space-between; + padding: 5px; + flex-wrap: wrap; + .cp-kanban-cursors { + margin-top: 10px; + } + .kanban-item-body, .kanban-item-tags { + .tools_unselectable(); + width: 100%; + } + .kanban-item-body { + margin: 10px 0; + font-size: 0.8em; + :last-child { + margin-bottom: 0px; + } + .markdown_main(); + .markdown_cryptpad(); + .markdown_preformatted-code; + .markdown_gfm-table(black); + ul { + padding-left: 30px; + } + } + .kanban-item-tags { display: flex; align-items: center; - min-height: -webkit-min-content; - min-height: min-content; - .kanban-container { - flex: 1; - display: flex; - flex-wrap: wrap; - justify-content: space-around; + flex-wrap: wrap; + span { + padding: 0 5px; + margin-right: 5px; + margin-top: 5px; + background-color: rgba(0,0,0,0.1); + display: inline-block; + font-size: 12px; } } + &.new-item { + padding: 10px; + } + .kanban-item-text { + .tools_unselectable(); + cursor: text; + overflow-wrap: anywhere; + flex: 1; + } + &.kanban-item-hidden { + display: none; + } + } - .kanban-item { + .kanban-board { + main { + padding: 0 10px; + margin: 10px 0; + flex: 1; + overflow-y: auto; + justify-content: space-around; + min-height: 50px; + } + header { display: flex; + flex-wrap: wrap; align-items: center; - justify-content: space-between; - padding: 10px 5px 10px 10px; - .kanban-item-text { - cursor: text; - overflow-wrap: anywhere; + padding: 5px 10px; + .kanban-title-board { flex: 1; + margin-right: 10px; + min-width: 0; + overflow: hidden; + //white-space: nowrap; + text-overflow: ellipsis; + } + #kanban-edit { + font-weight: bold; + } + &:hover { + cursor: move; } } + footer { + margin: 10px; + margin-top: 0px; + span { + .tools_unselectable(); + outline: none; + width: 100%; + border: 1px solid @cryptpad_text_col; + border-radius: 0px; + font-size: 40px; + display: inline-flex; + justify-content: center; + align-items: center; + line-height: 1; + cursor: pointer; + &:hover { + background-color: rgba(0,0,0,0.1); + } + } + } + } - .kanban-board { - header { + #cp-kanban-controls { + padding: 10px; + display: flex; + position: relative; + width: 100%; + justify-content: space-between; + position: relative; + min-height: 50px; + .cp-kanban-filterTags { + display: inline-flex; + align-items: baseline; + flex: 1; + max-width: 80%; + min-width: 150px; + + &> i { + cursor: pointer; + margin-left: 10px; + } + .cp-kanban-filterTags-name { + flex-shrink: 0; + } + .cp-kanban-filterTags-list { + margin-left: 10px; display: flex; - align-items: center; - padding: 13px 10px; - .kanban-title-board { - flex: 1; - margin-right: 10px; - min-width: 0; - overflow: hidden; - //white-space: nowrap; - text-overflow: ellipsis; + flex-wrap: wrap; + em { + font-size: 14px; + color: lighten(@cryptpad_text_col, 10%); } - #kanban-edit { - font-weight: bold; + + span { + .tools_unselectable(); + padding: 0 5px; + margin-right: 5px; + margin-top: 5px; + background-color: rgba(0,0,0,0.1); + display: inline-block; + font-size: 14px; + cursor: pointer; + &.active { + background-color: @cryptpad_text_col; + color: #fff; + } } + } + } + .cp-kanban-changeView { + right: 10px; + height: 30px; + width: 60px; + span { + height: 30px; + width: 30px; + line-height: 30px; + text-align: center; + display: inline-block; + background-color: @board-bg; + cursor: pointer; &:hover { - cursor: move; + background-color: darken(@board-bg, 10%); } } } + } + #cp-app-kanban-container { + &:not(.cp-kanban-quick) { + #cp-kanban-controls { + .cp-kanban-changeView { + span.cp-kanban-view { + background-color: @cryptpad_text_col !important; + color: white; + } + span.cp-kanban-view-small { + } + } + } + } + &.cp-kanban-quick { + #cp-kanban-controls { + .cp-kanban-changeView { + span.cp-kanban-view { + } + span.cp-kanban-view-small { + background-color: @cryptpad_text_col !important; + color: white; + } + } + } + .kanban-item { + .kanban-item-body, .kanban-item-tags { + display: none; + } + } + } + } + + #cp-app-kanban-content { + flex: 1; + display: flex; + flex-flow: column; + max-height: 100%; + overflow-x: auto; + .kanban-container-outer { + flex: 1; + display: flex; + min-height: 0; + .kanban-container { + flex: 1; + display: flex; + max-height: 100%; + } + } + #kanban-trash { + height: 60px; + font-size: 40px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 100%; + //pointer-events: none; + i { + position: fixed; + } + div { + width: 100%; + height: 60px; + position: fixed; + right: 0; + } + &.kanban-trash-active { + color: red; + div { + background: rgba(255,0,0,0.5); + } + } + .kanban-item, .kanban-board { + display: none; + } + } #kanban-edit { width: 100%; @@ -87,40 +434,21 @@ color: inherit; } - @button-size: 50px; #kanban-addboard { - margin: 30px; - border: 1px solid; - width: @button-size; - height: @button-size; - line-height: @button-size; - text-align: center; - background: @colortheme_kanban-bg; + order: 2; + width: 300px; + margin: 10px 5px; + border: 1px solid @cryptpad_text_col; + height: 40px; + display: inline-flex; + justify-content: center; + align-items: center; align-self: flex-start; - font-size: 30px; + font-size: 40px; cursor: pointer; .tools_unselectable(); - } - - .kanban-remove-item { - padding: 0 0.5em; - visibility: hidden; - } - .kanban-item:hover { - .kanban-remove-item { - visibility: visible; - } - } - - .kanban-additem { - float: right; - background: #EEE; - padding: 5px .5rem 4px; - line-height: 1; - margin-bottom: 5px; - margin-right: 5px; &:hover { - background: transparent; + background-color: rgba(0,0,0,0.1); } } @@ -176,6 +504,9 @@ .kanban-title-button, #kanban-addboard, .kanban-remove-item, .kanban-additem { display: none !important; } + #kanban-trash { + display: none; + } } } diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 621d56cae..f1eaa7a45 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -4,15 +4,30 @@ define([ '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/sframe-app-framework.js', + '/common/sframe-common-codemirror.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-interface.js', + '/common/common-ui-elements.js', '/common/modes.js', '/customize/messages.js', + '/common/hyperscript.js', + '/common/text-cursor.js', + '/common/diffMarked.js', + '/bower_components/chainpad/chainpad.dist.js', + '/bower_components/marked/marked.min.js', + 'cm/lib/codemirror', + + 'cm/mode/gfm/gfm', + + 'css!/bower_components/codemirror/lib/codemirror.css', + 'css!/bower_components/codemirror/addon/dialog/dialog.css', + 'css!/bower_components/codemirror/addon/fold/foldgutter.css', + + + '/kanban/jkanban.js', - '/common/jscolor.js', 'css!/kanban/jkanban.css', - 'less!/kanban/app-kanban.less' ], function ( $, @@ -20,77 +35,494 @@ define([ nThen, SFCommon, Framework, + SFCodeMirror, Util, Hash, UI, + UIElements, Modes, - Messages) + Messages, + h, + TextCursor, + DiffMd, + ChainPad, + Marked, + CodeMirror) { var verbose = function (x) { console.log(x); }; verbose = function () {}; // comment out to enable verbose logging + var onRedraw = Util.mkEvent(); + var onCursorUpdate = Util.mkEvent(); + var remoteCursors = {}; + + Messages.kanban_title = "Title"; // XXX + Messages.kanban_body = "Body"; // XXX + Messages.kanban_color = "Color"; // XXX + Messages.kanban_submit = "Submit"; // XXX + Messages.kanban_delete = "Delete"; // XXX + Messages.kanban_tags = "Filter tags"; // XXX + Messages.kanban_noTags = "No tags"; // XXX + Messages.kanban_conflicts = "Currently editing:"; // XXX + +// XXX +// Conflicts +// use cursor channel to tell others what you are editing +// add outline + warning inside the modal? + + var setValueAndCursor = function (input, val, _cursor) { + if (!input) { return; } + var $input = $(input); + var focus = _cursor || $input.is(':focus'); + var oldVal = $input.val(); + var ops = ChainPad.Diff.diff(_cursor ? _cursor.value : oldVal, val); + + var cursor = _cursor || input; + + var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { + return TextCursor.transformCursor(cursor[attr], ops); + }); + $input.val(val); + if (focus) { $input.focus(); } + input.selectionStart = selects[0]; + input.selectionEnd = selects[1]; + }; + + var getTextColor = function (hex) { + if (hex && /^#/.test(hex)) { hex = hex.slice(1); } + if (!/^[0-9a-f]{6}$/i.test(hex)) { + return '#000000'; + } + var r = parseInt(hex.slice(0,2), 16); + var g = parseInt(hex.slice(2,4), 16); + var b = parseInt(hex.slice(4,6), 16); + if ((r*0.213 + g*0.715 + b*0.072) > 255/2) { + return '#000000'; + } + return '#FFFFFF'; + }; + + var getAvatar = function (cursor, noClear) { + // Tippy + var html = ''; + if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) { + html += UIElements.getAvatar(cursor.avatar); + } + html += cursor.name + ''; + + var l = (cursor.name || Messages.anonymous).slice(0,1).toUpperCase(); + + var text = ''; + if (cursor.color) { + text = 'color:'+getTextColor(cursor.color)+';'; + } + var avatar = h('span.cp-cursor.cp-tippy-html', { + style: "background-color: " + (cursor.color || 'red') + ";"+text, + title: html + }, l); + if (!noClear) { + cursor.clear = function () { + $(avatar).remove(); + }; + } + return avatar; + }; + + var getExistingTags = function (boards) { + var tags = []; + boards = boards || {}; + Object.keys(boards.items || {}).forEach(function (id) { + var data = boards.items[id]; + if (!Array.isArray(data.tags)) { return; } + data.tags.forEach(function (tag) { + if (tags.indexOf(tag) === -1) { tags.push(tag); } + }); + }); + tags.sort(); + return tags; + }; - var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue']; + var addEditItemButton = function () {}; + var onRemoteChange = Util.mkEvent(); + var editModal; + var PROPERTIES = ['title', 'body', 'tags', 'color']; + var BOARD_PROPERTIES = ['title', 'color']; + var createEditModal = function (framework, kanban) { + var dataObject = {}; + var isBoard, id; - var addRemoveItemButton = function (framework, kanban) { + var commit = function () { + framework.localChange(); + kanban.setBoards(kanban.options.boards); + addEditItemButton(framework, kanban); + }; + if (editModal) { return editModal; } + var conflicts, conflictContainer, titleInput, tagsDiv, colors, text; + var content = h('div', [ + conflictContainer = h('div#cp-kanban-edit-conflicts', [ + h('div', Messages.kanban_conflicts), + conflicts = h('div.cp-kanban-cursors') + ]), + h('label', {for:'cp-kanban-edit-title'}, Messages.kanban_title), + titleInput = h('input#cp-kanban-edit-title'), + h('label', {for:'cp-kanban-edit-body'}, Messages.kanban_body), + h('div#cp-kanban-edit-body', [ + text = h('textarea') + ]), + h('label', {for:'cp-kanban-edit-tags'}, Messages.fm_tagsName), + tagsDiv = h('div#cp-kanban-edit-tags'), + h('label', {for:'cp-kanban-edit-color'}, Messages.kanban_color), + colors = h('div#cp-kanban-edit-colors'), + ]); + + + var $conflict = $(conflicts); + var $cc = $(conflictContainer); + var conflict = { + setValue: function () { + $conflict.empty(); + var i = 0; + $cc.hide(); + Object.keys(remoteCursors).forEach(function (nid) { + var c = remoteCursors[nid]; + var avatar = getAvatar(c, true); + if (Number(c.item) === Number(id) || Number(c.board) === Number(id)) { + $conflict.append(avatar); + i++; + } + }); + if (!i) { return; } + $cc.show(); + } + }; + + // Title + var $title = $(titleInput); + $title.on('change keyup', function () { + dataObject.title = $title.val(); + commit(); + }); + var title = { + getValue: function () { + return $title.val(); + }, + setValue: function (val, preserveCursor) { + if (!preserveCursor) { + $title.val(val); + } else { + setValueAndCursor(titleInput, val); + } + } + }; + + // Body + var editor = CodeMirror.fromTextArea(text, { + lineWrapping: true, + styleActiveLine : true, + mode: "gfm" + }); + var common = framework._.sfCommon; + var markdownTb = common.createMarkdownToolbar(editor); + $(text).before(markdownTb.toolbar); + $(markdownTb.toolbar).show(); + editor.refresh(); + var body = { + getValue: function () { + return editor.getValue(); + }, + setValue: function (val, preserveCursor) { + if (isBoard) { return; } + if (!preserveCursor) { + setTimeout(function () { + editor.setValue(val || ' '); + editor.setValue(val || ''); + editor.save(); + }); + } else { + SFCodeMirror.setValueAndCursor(editor, editor.getValue(), val || ''); + } + } + }; + editor.on('change', function () { + dataObject.body = editor.getValue(); + commit(); + }); + + // Tags + var $tags = $(tagsDiv); + var _field, initialTags; + var tags = { + getValue: function () { + if (!_field) { return; } + return _field.getTokens(); + }, + setValue: function (tags, preserveCursor) { + if (isBoard) { return; } + if (preserveCursor && initialTags && Sortify(tags || []) === initialTags) { + // Don't redraw if there is no change + return; + } + initialTags = Sortify(tags || []); + $tags.empty(); + var input = UI.dialog.textInput(); + $tags.append(input); + var existing = getExistingTags(kanban.options.boards); + _field = UI.tokenField(input, existing).preventDuplicates(function (val) { + UI.warn(Messages._getKey('tags_duplicate', [val])); + }); + _field.setTokens(tags || []); + + var commitTags = function () { + dataObject.tags = _field.getTokens(); + initialTags = Sortify(dataObject.tags); + commit(); + }; + _field.tokenfield.on('tokenfield:createdtoken', commitTags); + _field.tokenfield.on('tokenfield:editedoken', commitTags); + _field.tokenfield.on('tokenfield:removedtoken', commitTags); + } + }; + + // Colors + var $colors = $(colors); + var palette = ['']; + for (var i=1; i<=8; i++) { palette.push('color'+i); } + var selectedColor = ''; + palette.forEach(function (color) { + var $color = $(h('span.cp-kanban-palette.fa')); + $color.addClass('cp-kanban-palette-'+(color || 'nocolor')); + $color.click(function () { + if (color === selectedColor) { return; } + selectedColor = color; + $colors.find('.cp-kanban-palette').removeClass('fa-check'); + var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor')); + $col.addClass('fa-check'); + + dataObject.color = color; + commit(); + }).appendTo($colors); + }); + var color = { + getValue: function () { + return selectedColor; + }, + setValue: function (color) { + $colors.find('.cp-kanban-palette').removeClass('fa-check'); + var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor')); + $col.addClass('fa-check'); + selectedColor = color; + } + }; + + var setId = function (_isBoard, _id) { + isBoard = _isBoard; + id = _id; + if (_isBoard) { + onCursorUpdate.fire({ + board: _id + }); + dataObject = kanban.getBoardJSON(id); + $(content) + .find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]') + .hide(); + } else { + onCursorUpdate.fire({ + item: _id + }); + dataObject = kanban.getItemJSON(id); + $(content) + .find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]') + .show(); + } + }; + + var button = [{ + className: 'danger left', + name: Messages.kanban_delete, + onClick: function () { + var boards = kanban.options.boards || {}; + if (isBoard) { + var list = boards.list || []; + var idx = list.indexOf(id); + if (idx !== -1) { list.splice(idx, 1); } + delete (boards.data || {})[id]; + return void commit(); + } + Object.keys(boards.data || {}).forEach(function (boardId) { + var board = boards.data[boardId]; + if (!board) { return; } + var items = board.item || []; + var idx = items.indexOf(id); + if (idx !== -1) { items.splice(idx, 1); } + }); + delete (boards.items || {})[id]; + commit(); + }, + keys: [] + }, { + className: 'primary', + name: Messages.filePicker_close, + onClick: function () { + onCursorUpdate.fire({}); + }, + keys: [] + }]; + var modal = UI.dialog.customModal(content, { + buttons: button + }); + modal.classList.add('cp-kanban-edit-modal'); + + onRemoteChange.reg(function () { + if (isBoard) { + dataObject = kanban.getBoardJSON(id); + } else { + dataObject = kanban.getItemJSON(id); + } + // Check if our itme has been deleted + if (!dataObject) { + var $frame = $(modal).parents('.alertify').first(); + if ($frame[0] && $frame[0].closeModal) { + $frame[0].closeModal(); + } + return; + } + // Not deleted, apply updates + editModal.conflict.setValue(); + PROPERTIES.forEach(function (type) { + editModal[type].setValue(dataObject[type], true); + }); + }); + + return { + modal: modal, + setId: setId, + title: title, + body: body, + tags: tags, + color: color, + conflict: conflict + }; + }; + var getItemEditModal = function (framework, kanban, eid) { + // Create modal if needed + if (!editModal) { editModal = createEditModal(framework, kanban); } + editModal.setId(false, eid); + var boards = kanban.options.boards || {}; + var item = (boards.items || {})[eid]; + if (!item) { return void UI.warn(Messages.error); } + editModal.conflict.setValue(); + PROPERTIES.forEach(function (type) { + if (!editModal[type]) { return; } + editModal[type].setValue(item[type]); + }); + UI.openCustomModal(editModal.modal); + }; + var getBoardEditModal = function (framework, kanban, id) { + // Create modal if needed + if (!editModal) { editModal = createEditModal(framework, kanban); } + + editModal.setId(true, id); + var boards = kanban.options.boards || {}; + var board = (boards.data || {})[id]; + if (!board) { return void UI.warn(Messages.error); } + editModal.conflict.setValue(); + BOARD_PROPERTIES.forEach(function (type) { + if (!editModal[type]) { return; } + editModal[type].setValue(board[type]); + }); + UI.openCustomModal(editModal.modal); + }; + + addEditItemButton = function (framework, kanban) { if (!kanban) { return; } if (framework.isReadOnly() || framework.isLocked()) { return; } var $container = $(kanban.element); - $container.find('.kanban-remove-item').remove(); - $container.find('.kanban-board .kanban-item').each(function (i, el) { - var pos = kanban.findElementPosition(el); - var board = kanban.options.boards.find(function (b) { - return b.id === $(el.parentNode.parentNode).attr('data-id'); - }); + $container.find('.kanban-edit-item').remove(); + $container.find('.kanban-item').each(function (i, el) { + var itemId = $(el).attr('data-eid'); $('' - headerBoard.appendChild(btn); - __onButtonClickHandler(btn, board.id); - } - //content board - var contentBoard = document.createElement('main'); - contentBoard.classList.add('kanban-drag'); - //add drag to array for dragula - self.boardContainer.push(contentBoard); - for (var itemkey in board.item) { - //create item - var itemKanban = board.item[itemkey]; - var nodeItem = document.createElement('div'); - nodeItem.classList.add('kanban-item'); - nodeItem.dataset.eid = itemKanban.id; - var nodeItemText = document.createElement('div'); - nodeItemText.classList.add('kanban-item-text'); - nodeItemText.dataset.eid = itemKanban.id; - nodeItemText.innerHTML = itemKanban.title; - nodeItem.appendChild(nodeItemText); - //add function - nodeItemText.clickfn = itemKanban.click; - nodeItemText.dragfn = itemKanban.drag; - nodeItemText.dragendfn = itemKanban.dragend; - nodeItemText.dropfn = itemKanban.drop; - //add click handler of item - __onclickHandler(nodeItemText); - if (itemKanban.color !== '' && itemKanban.color !== undefined) { - jscolorL = new jscolor(nodeItem,{showOnClick: false, valueElement:undefined}); - jscolorL.fromString(itemKanban.color); - } - __onColorClickHandler(nodeItem, "item"); - - contentBoard.appendChild(nodeItem); - } - //footer board - //add button - var addBoardItem = document.createElement('button'); - $(addBoardItem).addClass("kanban-title-button btn btn-default fa fa-plus"); - headerBoard.appendChild(addBoardItem); - __onAddItemClickHandler(addBoardItem); - - //board assembly - boardNode.appendChild(headerBoard); - boardNode.appendChild(contentBoard); + var boardkey = boards.list[index]; + var board = boards.data[boardkey]; + if (!board) { continue; } // XXX clean invalid data + + var boardNode = getBoardNode(board); + //board add self.container.appendChild(boardNode); } @@ -416,13 +573,14 @@ } this.setBoards = function (boards) { - self.element - for (var boardkey in this.options.boards) { - var board = this.options.boards[boardkey]; - this.removeBoard(board.id); + //self.element + for (var i in this.options.boards.list) { + var boardkey = this.options.boards.list[i]; + this.removeBoard(boardkey); } - this.options.boards = []; - this.addBoards(boards); + this.options.boards = boards; + this.addBoards(); + self.options.refresh(); } this.findBoard = function (id) { @@ -457,8 +615,13 @@ }; this.removeBoard = function (board) { - if (typeof (board) === 'string') + var id; + if (typeof (board) === 'string' || typeof (board) === "number") { + id = board; board = self.element.querySelector('[data-id="' + board + '"]'); + } else if (board) { + id = board.id; + } if (board) { board.remove(); @@ -466,14 +629,15 @@ self.onChange(); } + // Remove duplicates + if (id) { $(self.element).find('.kanban-board[data-id="' + board + '"]').remove(); } + return self; } - // board button on click function - this.onButtonClick = function (el) { - + this.renderMd = function (md) { + return self.options.renderMd(md); } - this.onChange = function () { self.options.onChange(); } @@ -485,6 +649,9 @@ this.getBoardJSON = function (id) { return __findBoardJSON(id); } + this.getItemJSON = function (id) { + return (self.options.boards.items || {})[id]; + }; //PRIVATE FUNCTION function __extendDefaults(source, properties) { @@ -507,14 +674,24 @@ boardContainerOuter.appendChild(boardContainer); var addBoard = document.createElement('div'); addBoard.id = 'kanban-addboard'; - addBoard.setAttribute('class', 'fa fa-plus'); + addBoard.innerText = '+'; + boardContainer.appendChild(addBoard); + var trash = self.trashContainer = document.createElement('div'); + trash.setAttribute('id', 'kanban-trash'); + trash.setAttribute('class', 'kanban-trash'); + var trashBg = document.createElement('div'); + var trashIcon = document.createElement('i'); + trashIcon.setAttribute('class', 'fa fa-trash'); + trash.appendChild(trashIcon); + trash.appendChild(trashBg); + self.boardContainer.push(trash); self.container = boardContainer; //add boards - self.addBoards(self.options.boards); + self.addBoards(); //appends to container self.element.appendChild(boardContainerOuter); - self.element.appendChild(addBoard); + self.element.appendChild(trash); // send event that board has changed self.onChange(); @@ -539,17 +716,6 @@ }); } - function __onColorClickHandler(nodeItem, type) { - nodeItem.addEventListener('click', function (e) { - if (Array.prototype.slice.call(nodeItem.classList).indexOf('is-moving') !== -1) { - return; - } - e.preventDefault; - e.stopPropagation(); - self.options.colorClick(this, type); - }); - } - function __onAddItemClickHandler(nodeItem, clickfn) { nodeItem.addEventListener('click', function (e) { e.preventDefault; @@ -560,24 +726,8 @@ }); } - function __onButtonClickHandler(nodeItem, boardId) { - nodeItem.addEventListener('click', function (e) { - e.stopPropagation(); - e.preventDefault; - self.options.buttonClick(this, boardId, e); - // if(typeof(this.clickfn) === 'function') - // this.clickfn(this); - }); - } - function __findBoardJSON(id) { - var el = [] - self.options.boards.map(function (board) { - if (board.id === id) { - return el.push(board) - } - }) - return el[0] + return (self.options.boards.data || {})[id]; }