From ba012af91105ba5b59c4ad3443693a03ae7d14bb Mon Sep 17 00:00:00 2001 From: Mario Simanjuntak Date: Mon, 7 Dec 2020 18:05:56 +0700 Subject: [PATCH 1/5] Added Article Tree Lists --- app/helpers/knowledgebase_helper.rb | 4 + app/views/articles/_article_tree.erb | 54 ++ app/views/articles/_article_tree_row.erb | 38 + app/views/articles/_index_original.html.erb | 8 + .../_knowledgebase_settings.html.erb | 5 + assets/images/article_tree_document.png | Bin 0 -> 505 bytes assets/images/article_tree_folder_yellow.png | Bin 0 -> 353 bytes .../article_tree_folder_yellow_document.png | Bin 0 -> 473 bytes assets/javascripts/tree.js | 663 ++++++++++++++++++ assets/stylesheets/knowledgebase.css | 33 + assets/stylesheets/treejs.css | 62 ++ config/locales/en.yml | 2 + init.rb | 7 +- 13 files changed, 875 insertions(+), 1 deletion(-) create mode 100644 app/views/articles/_article_tree.erb create mode 100644 app/views/articles/_article_tree_row.erb create mode 100644 assets/images/article_tree_document.png create mode 100644 assets/images/article_tree_folder_yellow.png create mode 100644 assets/images/article_tree_folder_yellow_document.png create mode 100644 assets/javascripts/tree.js create mode 100644 assets/stylesheets/treejs.css diff --git a/app/helpers/knowledgebase_helper.rb b/app/helpers/knowledgebase_helper.rb index a808ee22..4b18ae55 100644 --- a/app/helpers/knowledgebase_helper.rb +++ b/app/helpers/knowledgebase_helper.rb @@ -49,6 +49,10 @@ def sort_categories? def show_category_totals? redmine_knowledgebase_settings_value(:show_category_totals) end + + def show_article_tree_lists? + redmine_knowledgebase_settings_value(:show_article_tree_lists) + end def updated_by(updated, updater) l(:label_updated_who, :updater => link_to_user(updater), :age => time_tag(updated)).html_safe diff --git a/app/views/articles/_article_tree.erb b/app/views/articles/_article_tree.erb new file mode 100644 index 00000000..4a281c35 --- /dev/null +++ b/app/views/articles/_article_tree.erb @@ -0,0 +1,54 @@ +
+ Expand All + Collapse All +
+ +
+ + \ No newline at end of file diff --git a/app/views/articles/_article_tree_row.erb b/app/views/articles/_article_tree_row.erb new file mode 100644 index 00000000..6d985f6c --- /dev/null +++ b/app/views/articles/_article_tree_row.erb @@ -0,0 +1,38 @@ +node<%= category.id %> = new TreeNode("<%= category.title %>", + { + icon: expandIcon + } +); + +node<%= category.id %>.on('expand', function(node){ + node.setOptions({ + icon: expandIcon + }); +}); + +node<%= category.id %>.on('collapse', function(node){ + node.setOptions({ + icon: collapseIcon + }); +}); + +<%= parent_node_id %>.addChild(node<%= category.id %>); + +<% subs = category.children %> +<% subs = subs.sort_by(&:title) if sort_categories? %> +<% subs.each do |subcat| %> + <%= render :partial => 'articles/article_tree_row', :locals => { :category => subcat, :parent_node_id => "node#{category.id}" } %> +<% end %> + +<% arts = category.articles %> +<% arts = arts.sort_by(&:title) if sort_categories? %> +<% arts.each do |article| %> + <% truncate_length = 100 if local_assigns[:truncate_length].nil? %> + + node<%= category.id %>.addChild(new TreeNode('<%= link_to truncate(article.title, :length => truncate_length), { :controller => 'articles', :action => 'show', :id => article.id, :project_id => @project}, :title => article.title %>', + { + icon: '' + } + )); +<% end %> + diff --git a/app/views/articles/_index_original.html.erb b/app/views/articles/_index_original.html.erb index 53a9c495..4ab48b2e 100644 --- a/app/views/articles/_index_original.html.erb +++ b/app/views/articles/_index_original.html.erb @@ -12,6 +12,14 @@ <% end %> + + <% if show_article_tree_lists? %> +
+

<%= l(:title_article_tree_lists) %>

+ <%= render :partial => 'articles/article_tree' %> +
+ <% end %> + <% if User.current.allowed_to?(:view_recent_articles, @project) %>

<%= l(:title_recently_updated_articles) %>

diff --git a/app/views/redmine_knowledgebase/_knowledgebase_settings.html.erb b/app/views/redmine_knowledgebase/_knowledgebase_settings.html.erb index 95c802a4..e9bbc986 100644 --- a/app/views/redmine_knowledgebase/_knowledgebase_settings.html.erb +++ b/app/views/redmine_knowledgebase/_knowledgebase_settings.html.erb @@ -45,6 +45,11 @@ <%= check_box_tag 'settings[show_tiled_article_lists]', 1, @settings[:show_tiled_article_lists] %>

+

+ <%= label_tag :settings_show_category_tree_lists, l(:show_article_tree_lists) %> + <%= check_box_tag 'settings[show_article_tree_lists]', 1, @settings[:show_article_tree_lists] %> +

+

<%= label_tag :settings_critical_tags, l(:critical_tags) %> <%= text_field_tag "settings[critical_tags]", @settings[:critical_tags] %> diff --git a/assets/images/article_tree_document.png b/assets/images/article_tree_document.png new file mode 100644 index 0000000000000000000000000000000000000000..61245a2ba9e70f0d66dc67362286caeae5a3b9a6 GIT binary patch literal 505 zcmVarH;6Y#4iLdM2S^nNBnQwoRlGs!RJnkK2qD2jFu15yGAmp8Z|2R+?z~6d2ZI)~ z`1AAgeKQ52H2s=gsh3B!nj$AX3|@v$cegjT&&w40HBabN*S-7afi-yQbSPQF%K%P$ zftLb@!0=eW!SI;CLGVT0hGo3kE{Lj8WxE{Ug>F*EI9JGJ&6N}Y)L+`9oJBvi6T=EB==NyN?7k%Z`r}K$)u|YY8U}fi7AzZCsS=07#Nv7T^vIyZoRo;$aTm;p!MNNwWbKyBX)DwE*J1FX8Fis z>A9!j1uxGuvw4U0%rAK9OxeMlot_;rHn->gSa^G)Pw^ZgZ)T&*-e^&WP7^BLZ9o|0E6V$(5 zYV+Q?k?Vu+%Oho3uNhpIy}xKMwQ!#2G&QxT{rBeHd8c1#bN|tnKP5&6!EuXYw{9*g zx2jxy@!h#gPwMBgyq>NA^eGV_fn|Z|o&BK=rwkmPww(@sZ10tgTe~DWM4f={$%i literal 0 HcmV?d00001 diff --git a/assets/images/article_tree_folder_yellow_document.png b/assets/images/article_tree_folder_yellow_document.png new file mode 100644 index 0000000000000000000000000000000000000000..a7143ab42361010fb5c6232464aa7b29e5886d8a GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0vp^4nS8U}fi7AzZCsS=07#OQPT^vIyZoRp--|KLI2-}C^{L2zY7786T-Ld1^bOHCx z+8TC_Y3yG)Ke03|*nRZkQTBoj&E4HkSXarqc>PzI7raPf#Zkw}+3(696fjHb-G6`Q zUdYAiRW`S?K3-aOfBm~-4}ItJ=&1AWJM4W%_RmGT=XcoEm{!>Q3FWMw`$^Nnkk{e) zWc|f`lGXQJ8WtS+o8*=;n=K&iq-yo-_-)7TFh;WeF59`d%Se2mM<`=RY$s2HqSZF0 znvfMI-*wI1X})B)%e?*n-Ustl`A^}CPv6V6kM)E3_Z{Wg#;@-ERsDYSynS;im%YCK zLzaxSCvsQ4EdKQ;V7L69?F%w3PHFgVRywfg`hus^oc)|@_etE9y~>x6dvxx)$DVHb ze+~EYsx49dv+BN1%lc~1u#-ABxBA?>HtEf+Whc*Dac=uB$OH^vB0x2RQ1@qbCV@yE rfz15hyAJa&OjUQ>)HdmFYy%_1O}`+SghMtvK<0S5`njxgN@xNAG!MYh literal 0 HcmV?d00001 diff --git a/assets/javascripts/tree.js b/assets/javascripts/tree.js new file mode 100644 index 00000000..254d8381 --- /dev/null +++ b/assets/javascripts/tree.js @@ -0,0 +1,663 @@ +/** +* TreeJS is a JavaScript librarie for displaying TreeViews +* on the web. +* +* @author Matthias Thalmann +*/ + +function TreeView(root, container, options){ + var self = this; + + /* + * Konstruktor + */ + if(typeof root === "undefined"){ + throw new Error("Parameter 1 must be set (root)"); + } + + if(!(root instanceof TreeNode)){ + throw new Error("Parameter 1 must be of type TreeNode"); + } + + if(container){ + if(!TreeUtil.isDOM(container)){ + container = document.querySelector(container); + + if(container instanceof Array){ + container = container[0]; + } + + if(!TreeUtil.isDOM(container)){ + throw new Error("Parameter 2 must be either DOM-Object or CSS-QuerySelector (#, .)"); + } + } + }else{ + container = null; + } + + if(!options || typeof options !== "object"){ + options = {}; + } + + /* + * Methods + */ + this.setRoot = function(_root){ + if(root instanceof TreeNode){ + root = _root; + } + } + + this.getRoot = function(){ + return root; + } + + this.expandAllNodes = function(){ + root.setExpanded(true); + + root.getChildren().forEach(function(child){ + TreeUtil.expandNode(child); + }); + } + + this.expandPath = function(path){ + if(!(path instanceof TreePath)){ + throw new Error("Parameter 1 must be of type TreePath"); + } + + path.getPath().forEach(function(node){ + node.setExpanded(true); + }); + } + + this.collapseAllNodes = function(){ + root.setExpanded(false); + + root.getChildren().forEach(function(child){ + TreeUtil.collapseNode(child); + }); + } + + this.setContainer = function(_container){ + if(TreeUtil.isDOM(_container)){ + container = _container; + }else{ + _container = document.querySelector(_container); + + if(_container instanceof Array){ + _container = _container[0]; + } + + if(!TreeUtil.isDOM(_container)){ + throw new Error("Parameter 1 must be either DOM-Object or CSS-QuerySelector (#, .)"); + } + } + } + + this.getContainer = function(){ + return container; + } + + this.setOptions = function(_options){ + if(typeof _options === "object"){ + options = _options; + } + } + + this.changeOption = function(option, value){ + options[option] = value; + } + + this.getOptions = function(){ + return options; + } + + // TODO: set selected key: up down; expand right; collapse left; enter: open; + this.getSelectedNodes = function(){ + return TreeUtil.getSelectedNodesForNode(root); + } + + this.reload = function(){ + if(container == null){ + console.warn("No container specified"); + return; + } + + container.classList.add("tj_container"); + + var cnt = document.createElement("ul"); + cnt.appendChild(renderNode(root)); + + container.innerHTML = ""; + container.appendChild(cnt); + } + + function renderNode(node){ + var li_outer = document.createElement("li"); + var span_desc = document.createElement("span"); + span_desc.className = "tj_description"; + span_desc.tj_node = node; + + if(!node.isEnabled()){ + li_outer.setAttribute("disabled", ""); + node.setExpanded(false); + node.setSelected(false); + } + + if(node.isSelected()){ + span_desc.classList.add("selected"); + } + + span_desc.addEventListener("click", function(e){ + var cur_el = e.target; + + while(typeof cur_el.tj_node === "undefined" || cur_el.classList.contains("tj_container")){ + cur_el = cur_el.parentElement; + } + + var node_cur = cur_el.tj_node; + + if(typeof node_cur === "undefined"){ + return; + } + + if(node_cur.isEnabled()){ + if(e.ctrlKey == false){ + if(!node_cur.isLeaf()){ + node_cur.toggleExpanded(); + self.reload(); + }else{ + node_cur.open(); + } + + node_cur.on("click")(e, node_cur); + } + + + if(e.ctrlKey == true){ + node_cur.toggleSelected(); + self.reload(); + }else{ + var rt = node_cur.getRoot(); + + if(rt instanceof TreeNode){ + TreeUtil.getSelectedNodesForNode(rt).forEach(function(_nd){ + _nd.setSelected(false); + }); + } + node_cur.setSelected(true); + + self.reload(); + } + } + }); + + span_desc.addEventListener("contextmenu", function(e){ + var cur_el = e.target; + + while(typeof cur_el.tj_node === "undefined" || cur_el.classList.contains("tj_container")){ + cur_el = cur_el.parentElement; + } + + var node_cur = cur_el.tj_node; + + if(typeof node_cur === "undefined"){ + return; + } + + if(typeof node_cur.getListener("contextmenu") !== "undefined"){ + node_cur.on("contextmenu")(e, node_cur); + e.preventDefault(); + }else if(typeof TreeConfig.context_menu === "function"){ + TreeConfig.context_menu(e, node_cur); + e.preventDefault(); + } + }); + + if(node.isLeaf()){ + var ret = ''; + var icon = TreeUtil.getProperty(node.getOptions(), "icon", ""); + if(icon != ""){ + ret += '' + icon + ''; + }else if((icon = TreeUtil.getProperty(options, "leaf_icon", "")) != ""){ + ret += '' + icon + ''; + }else{ + ret += '' + TreeConfig.leaf_icon + ''; + } + + span_desc.innerHTML = ret + node.toString() + ""; + span_desc.classList.add("tj_leaf"); + + li_outer.appendChild(span_desc); + }else{ + var ret = ''; + if(node.isExpanded()){ + ret += '' + TreeConfig.open_icon + ''; + }else{ + ret+= '' + TreeConfig.close_icon + ''; + } + + var icon = TreeUtil.getProperty(node.getOptions(), "icon", ""); + if(icon != ""){ + ret += '' + icon + ''; + }else if((icon = TreeUtil.getProperty(options, "parent_icon", "")) != ""){ + ret += '' + icon + ''; + }else{ + ret += '' + TreeConfig.parent_icon + ''; + } + + span_desc.innerHTML = ret + node.toString() + ''; + + li_outer.appendChild(span_desc); + + if(node.isExpanded()){ + var ul_container = document.createElement("ul"); + + node.getChildren().forEach(function(child){ + ul_container.appendChild(renderNode(child)); + }); + + li_outer.appendChild(ul_container) + } + } + + return li_outer; + } + + if(typeof container !== "undefined") + this.reload(); +} + +function TreeNode(userObject, options){ + var children = new Array(); + var self = this; + var events = new Array(); + + var expanded = true; + var enabled = true; + var selected = false; + + /* + * Konstruktor + */ + if(userObject){ + if(!(typeof userObject === "string") || typeof userObject.toString !== "function"){ + throw new Error("Parameter 1 must be of type String or Object, where it must have the function toString()"); + } + }else{ + userObject = ""; + } + + if(!options || typeof options !== "object"){ + options = {}; + }else{ + expanded = TreeUtil.getProperty(options, "expanded", true); + enabled = TreeUtil.getProperty(options, "enabled", true); + selected = TreeUtil.getProperty(options, "selected", false); + } + + /* + * Methods + */ + this.addChild = function(node){ + if(!TreeUtil.getProperty(options, "allowsChildren", true)){ + console.warn("Option allowsChildren is set to false, no child added"); + return; + } + + if(node instanceof TreeNode){ + children.push(node); + + //Konstante hinzufügen (workaround) + Object.defineProperty(node, "parent", { + value: this, + writable: false, + enumerable: true, + configurable: true + }); + }else{ + throw new Error("Parameter 1 must be of type TreeNode"); + } + } + + this.removeChildPos = function(pos){ + if(typeof children[pos] !== "undefined"){ + if(typeof children[pos] !== "undefined"){ + children.splice(pos, 1); + } + } + } + + this.removeChild = function(node){ + if(!(node instanceof TreeNode)){ + throw new Error("Parameter 1 must be of type TreeNode"); + } + + this.removeChildPos(this.getIndexOfChild(node)); + } + + this.getChildren = function(){ + return children; + } + + this.getChildCount = function(){ + return children.length; + } + + this.getIndexOfChild = function(node){ + for(var i = 0; i < children.length; i++){ + if(children[i].equals(node)){ + return i; + } + } + + return -1; + } + + this.getRoot = function(){ + var node = this; + + while(typeof node.parent !== "undefined"){ + node = node.parent; + } + + return node; + } + + this.setUserObject = function(_userObject){ + if(!(typeof _userObject === "string") || typeof _userObject.toString !== "function"){ + throw new Error("Parameter 1 must be of type String or Object, where it must have the function toString()"); + }else{ + userObject = _userObject; + } + } + + this.getUserObject = function(){ + return userObject; + } + + this.setOptions = function(_options){ + if(typeof _options === "object"){ + options = _options; + } + } + + this.changeOption = function(option, value){ + options[option] = value; + } + + this.getOptions = function(){ + return options; + } + + this.isLeaf = function(){ + return (children.length == 0); + } + + this.setExpanded = function(_expanded){ + if(this.isLeaf()){ + return; + } + + if(typeof _expanded === "boolean"){ + if(expanded == _expanded){ + return; + } + + expanded = _expanded; + + if(_expanded){ + this.on("expand")(this); + }else{ + this.on("collapse")(this); + } + + this.on("toggle_expanded")(this); + } + } + + this.toggleExpanded = function(){ + if(expanded){ + this.setExpanded(false); + }else{ + this.setExpanded(true); + } + }; + + this.isExpanded = function(){ + if(this.isLeaf()){ + return true; + }else{ + return expanded; + } + } + + this.setEnabled = function(_enabled){ + if(typeof _enabled === "boolean"){ + if(enabled == _enabled){ + return; + } + + enabled = _enabled; + + if(_enabled){ + this.on("enable")(this); + }else{ + this.on("disable")(this); + } + + this.on("toggle_enabled")(this); + } + } + + this.toggleEnabled = function(){ + if(enabled){ + this.setEnabled(false); + }else{ + this.setEnabled(true); + } + } + + this.isEnabled = function(){ + return enabled; + } + + this.setSelected = function(_selected){ + if(typeof _selected !== "boolean"){ + return; + } + + if(selected == _selected){ + return; + } + + selected = _selected; + + if(_selected){ + this.on("select")(this); + }else{ + this.on("deselect")(this); + } + + this.on("toggle_selected")(this); + } + + this.toggleSelected = function(){ + if(selected){ + this.setSelected(false); + }else{ + this.setSelected(true); + } + } + + this.isSelected = function(){ + return selected; + } + + this.open = function(){ + if(!this.isLeaf()){ + this.on("open")(this); + } + } + + this.on = function(ev, callback){ + if(typeof callback === "undefined"){ + if(typeof events[ev] !== "function"){ + return function(){}; + }else{ + return events[ev]; + } + } + + if(typeof callback !== 'function'){ + throw new Error("Argument 2 must be of type function"); + } + + events[ev] = callback; + } + + this.getListener = function(ev){ + return events[ev]; + } + + this.equals = function(node){ + if(node instanceof TreeNode){ + if(node.getUserObject() == userObject){ + return true; + } + } + + return false; + } + + this.toString = function(){ + if(typeof userObject === "string"){ + return userObject; + }else{ + return userObject.toString(); + } + } +} + +function TreePath(root, node){ + var nodes = new Array(); + + this.setPath = function(root, node){ + nodes = new Array(); + + while(typeof node !== "undefined" && !node.equals(root)){ + nodes.push(node); + node = node.parent; + } + + if(node.equals(root)){ + nodes.push(root); + }else{ + nodes = new Array(); + throw new Error("Node is not contained in the tree of root"); + } + + nodes = nodes.reverse(); + + return nodes; + } + + this.getPath = function(){ + return nodes; + } + + this.toString = function(){ + return nodes.join(" - "); + } + + if(root instanceof TreeNode && node instanceof TreeNode){ + this.setPath(root, node); + } +} + +/* +* Util-Methods +*/ +const TreeUtil = { + default_leaf_icon: "🖹", + default_parent_icon: "🗁", + default_open_icon: "", + default_close_icon: "", + + isDOM: function(obj){ + try { + return obj instanceof HTMLElement; + } + catch(e){ + return (typeof obj==="object") && + (obj.nodeType===1) && (typeof obj.style === "object") && + (typeof obj.ownerDocument ==="object"); + } + }, + + getProperty: function(options, opt, def){ + if(typeof options[opt] === "undefined"){ + return def; + } + + return options[opt]; + }, + + expandNode: function(node){ + node.setExpanded(true); + + if(!node.isLeaf()){ + node.getChildren().forEach(function(child){ + TreeUtil.expandNode(child); + }); + } + }, + + collapseNode: function(node){ + node.setExpanded(false); + + if(!node.isLeaf()){ + node.getChildren().forEach(function(child){ + TreeUtil.collapseNode(child); + }); + } + }, + + getSelectedNodesForNode: function(node){ + if(!(node instanceof TreeNode)){ + throw new Error("Parameter 1 must be of type TreeNode"); + } + + var ret = new Array(); + + if(node.isSelected()){ + ret.push(node); + } + + node.getChildren().forEach(function(child){ + if(child.isSelected()){ + if(ret.indexOf(child) == -1){ + ret.push(child); + } + } + + if(!child.isLeaf()){ + TreeUtil.getSelectedNodesForNode(child).forEach(function(_node){ + if(ret.indexOf(_node) == -1){ + ret.push(_node); + } + }); + } + }); + + return ret; + } +}; + +var TreeConfig = { + leaf_icon: TreeUtil.default_leaf_icon, + parent_icon: TreeUtil.default_parent_icon, + open_icon: TreeUtil.default_open_icon, + close_icon: TreeUtil.default_close_icon, + context_menu: undefined +}; diff --git a/assets/stylesheets/knowledgebase.css b/assets/stylesheets/knowledgebase.css index 4338d791..019a12e6 100644 --- a/assets/stylesheets/knowledgebase.css +++ b/assets/stylesheets/knowledgebase.css @@ -238,6 +238,39 @@ div.attachments { overflow: hidden; } +#article-tree.tj_container .tj_icon { + height: 13px; +} + +.icon-folder-expand:before, .icon-folder-collapse:before, .icon-article:before { + content: ""; + display: block; + background-size: contain; + background-repeat: no-repeat; +} + +.icon-folder-expand:before, .icon-folder-collapse:before { + width: 19px; + height: 15px; +} + +.icon-article:before { + width: 12px; + height: 14px; +} + +.icon-folder-expand:before { + background-image: url("../images/article_tree_folder_yellow_document.png"); +} + +.icon-folder-collapse:before { + background-image: url("../images/article_tree_folder_yellow.png"); +} + +.icon-article:before { + background-image: url("../images/article_tree_document.png"); +} + /* If you want custom default thumbnails by category, add them here! */ /* diff --git a/assets/stylesheets/treejs.css b/assets/stylesheets/treejs.css new file mode 100644 index 00000000..4b5ec29d --- /dev/null +++ b/assets/stylesheets/treejs.css @@ -0,0 +1,62 @@ +.tj_container *{ + position: relative; + box-sizing: border-box; +} + +.tj_container ul{ + padding-left: 2em; + list-style-type: none; +} + +.tj_container > ul:first-of-type{ + padding: 0; +} + +.tj_container li span.tj_description{ + cursor: pointer; + padding: 2px 5px; + display: block; + border-radius: 2px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.tj_container li span.tj_description:hover{ + background-color: #ccc; +} + +.tj_container li span.tj_mod_icon, .tj_container li span.tj_icon{ + margin-right: 0.5em; + display: inline-block; +} + +.tj_container li span.tj_mod_icon, .tj_container li span.tj_mod_icon *{ + width: 1em; +} + +.tj_container li span.tj_description.tj_leaf{ + margin-left: 1.5em; +} + +.tj_container li[disabled=""]{ + color: #b5b5b5; +} + +.tj_container li[disabled=""]:hover span.tj_description{ + cursor: default; + background-color: inherit; +} + +.tj_container span.tj_description.selected{ + background-color: #2b2b2b; + color: #fff; +} + +.tj_container span.tj_description.selected:hover{ + background-color: #606060; +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 949e64a0..b5d5c020 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -98,6 +98,7 @@ en: show_breadcrumbs_for_article_lists: "Show breadcrumbs for articles in lists?" show_thumbnails_for_articles: "Show thumbnails for articles in lists?" show_tiled_article_lists: "Show tiled article lists?" + show_article_tree_lists: "Show article tree lists?" summary_item_limit: "Knowledgebase summary page article limit" text_confirm_versions_delete: "This will revert and delete any newer versions of this document that may exist. Are you sure?" text_current_version: Current @@ -105,6 +106,7 @@ en: title_add_comment: "Add Comment" title_article_rating: "Article Rating" title_browse_by_category: "Browse by Category" + title_article_tree_lists: "Article Tree Lists" title_browse_by_tags: "Browse by Tags" title_create_article: "Create Article" title_create_category: "Create Category" diff --git a/init.rb b/init.rb index 76e6c6ce..15e61388 100644 --- a/init.rb +++ b/init.rb @@ -96,5 +96,10 @@ end class RedmineKnowledgebaseHookListener < Redmine::Hook::ViewListener - render_on :view_layouts_base_html_head, :inline => "<%= stylesheet_link_tag 'knowledgebase', :plugin => :redmine_knowledgebase %>" + render_on :view_layouts_base_html_head, + {:inline => "<%= stylesheet_link_tag 'knowledgebase', :plugin => :redmine_knowledgebase %>"}, + {:inline => "<%= stylesheet_link_tag 'treejs', :plugin => :redmine_knowledgebase %>"}, + {:inline => "<%= javascript_include_tag 'tree', :plugin => :redmine_knowledgebase %>"} + + end From bee967058771e0af91bf76e8390c4e291d7a80ac Mon Sep 17 00:00:00 2001 From: Mario Simanjuntak Date: Tue, 8 Dec 2020 16:44:12 +0700 Subject: [PATCH 2/5] Updated Collapse All Label --- app/views/articles/_article_tree.erb | 4 ++-- config/locales/en.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/articles/_article_tree.erb b/app/views/articles/_article_tree.erb index 4a281c35..c2aedc83 100644 --- a/app/views/articles/_article_tree.erb +++ b/app/views/articles/_article_tree.erb @@ -1,6 +1,6 @@

- Expand All - Collapse All + <%= l(:label_expand_all) %> + <%= l(:label_collapse_all) %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index b5d5c020..6a1fa8b8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,6 +67,8 @@ en: label_unrated_article: Unrated label_updated_who: "Updated by %{updater} %{age} ago" label_unrated_article: "Unrated" + label_expand_all: "Expand All" + label_collapse_all: "Fold All" message_no_articles: "No articles have been added yet. Click Create Article to get started." message_no_categories: "No categories have been created. Click Create Category to get started." message_no_permissions: "You do not have permission to view any articles or categories. Please contact your administrator." From fa75aef41ad4d27e0be387447c4aa2f05ca1cd5a Mon Sep 17 00:00:00 2001 From: Mario Simanjuntak Date: Mon, 14 Dec 2020 10:26:04 +0700 Subject: [PATCH 3/5] Added Link to Category title --- app/views/articles/_article_tree_row.erb | 2 +- assets/stylesheets/knowledgebase.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/articles/_article_tree_row.erb b/app/views/articles/_article_tree_row.erb index 6d985f6c..8cee77cf 100644 --- a/app/views/articles/_article_tree_row.erb +++ b/app/views/articles/_article_tree_row.erb @@ -1,4 +1,4 @@ -node<%= category.id %> = new TreeNode("<%= category.title %>", +node<%= category.id %> = new TreeNode('<%= link_to(category.title, {:controller => 'categories', :action => 'show', :id => category.id, :project_id => @project} ) %>', { icon: expandIcon } diff --git a/assets/stylesheets/knowledgebase.css b/assets/stylesheets/knowledgebase.css index 019a12e6..746a89f7 100644 --- a/assets/stylesheets/knowledgebase.css +++ b/assets/stylesheets/knowledgebase.css @@ -271,6 +271,10 @@ div.attachments { background-image: url("../images/article_tree_document.png"); } +.tj_container span.tj_description.selected a { + color: #fff; +} + /* If you want custom default thumbnails by category, add them here! */ /* From ef9fbe008e4224374ab67ca07320e0bf16fc7c0b Mon Sep 17 00:00:00 2001 From: Mario Simanjuntak Date: Fri, 18 Dec 2020 13:29:06 +0700 Subject: [PATCH 4/5] Updated Folder and Article icon --- assets/images/article_tree_document.png | Bin 505 -> 1184 bytes assets/images/article_tree_folder_expanded.png | Bin 0 -> 1292 bytes assets/images/article_tree_folder_folded.png | Bin 0 -> 771 bytes assets/images/article_tree_folder_yellow.png | Bin 353 -> 0 bytes .../article_tree_folder_yellow_document.png | Bin 473 -> 0 bytes assets/stylesheets/knowledgebase.css | 12 ++++++------ 6 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 assets/images/article_tree_folder_expanded.png create mode 100644 assets/images/article_tree_folder_folded.png delete mode 100644 assets/images/article_tree_folder_yellow.png delete mode 100644 assets/images/article_tree_folder_yellow_document.png diff --git a/assets/images/article_tree_document.png b/assets/images/article_tree_document.png index 61245a2ba9e70f0d66dc67362286caeae5a3b9a6..751c503f36541ddd034a3a6deada41f7a44794d6 100644 GIT binary patch literal 1184 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEX7WqAsj$Z!;#Vf4nJ za0`PlBg3pY5yC(QLKq? z=gPI+({!V@Y~`+a$)9n3+s4$~CNtM=g}ITeqB9&tjPG@|7-((n-X?jltH)~k_up0X z?)!CpuAB2bbKhmjbU%lG%zv*Jho>cVDsr-allsAly0BV+g1HaniNPQ1=QU`ktxQ)cKr?K5Y0SJ`ND zZ@9YukB5a$^Kv$ku&4bSS6`2@oma|w;?9YF(Nn@TH_JOiuQM$Dpl?v~JtCN)?LDtH z*MSdvlWQ1_%s(X`-DK7FUT`t@2El(-2VSH`oxNdv`orJECpXWgs7vhg@3B5S^UaY6 z!>IEgYLl)+rv3P5{LII=M%L{P*E{L?GP={$CHFCXHNIzVXK58ZZ_kMjr7Px@wypPn zd&aoNc*UIBwtT+(!U;dpK7Ord{Co4y37<;Y!{#0No?p^GD;r8AI?6l zRN}dxGjG|Xlh4{D_uaJdR-1g5SA22jX^`lW!yn#0ta9SHuea_V`-zm|!$%`NeX^== zyezd&^uV+a_PY9~ujgmqI{m{jBRU}I;)-)Q=6p%sKa`ZVzq?cXmwB3>sfw^sMbz`p zJ$>e{Cg0@DKD0dEQtv>}ieEB^w@-h}wC1GMi++eknJV)>g+G3&RMUtw%+XH-tgb~;Z&BC_s7nQpPy4u<>4=z<(sL0P-9Ota~~_i zai)f3#seD}9z-w{=rCA_G0eHOyg6O`-ih5yRo8xWTe)`S#9zDaYHhyC_2I?KDK?eg zVzT!H^2vFu6m5tGDVO19@Ovxupu*ky{!HzaYdTh*4bwi*cWcL+6}|s|>Q7p}gB8tu zpa!6|%gdBse7v&d%$fbyr1Gasd*+sznHMs1ZCJyhH%#~W^j5B06Qa$q9quI40}lE9 zJYT+L^ez|LhwfsCi9nA8?-s83pBT3+e$N%4p~~&Impm?*eJW=wQ_Y_(I`605%kDKl z_36~ATRC}*An#^HA2|26@aua~v(~HkMgOd~y8QOi%)IMT{;GGFkV6E?e8DZ7c4^Ju zUvm{`{w9xzQ%ZtUZ)UOX_?<9Go=^9s)vRgjk~62?glo^b&akq!S17zj`ijb|{!O7e z4wILBGtC5g0^$O2fWwUk#>2;p8)v&5O58C!xTfTdnrUcg0qX-XPa&Q%-@4p5XbFfF8a$`8`)a+8(73Jc)I$z JtaD0e0swmv7MK74 literal 505 zcmVarH;6Y#4iLdM2S^nNBnQwoRlGs!RJnkK2qD2jFu15yGAmp8Z|2R+?z~6d2ZI)~ z`1AAgeKQ52H2s=gsh3B!nj$AX3|@v$cegjT&&w40HBabN*S-7afi-yQbSPQF%K%P$ zftLb@!0=eW!SI;CLGVT0hGo3kE{Lj8WxE{Ug>F*EI9JGJ&6N}Y)L+`9oJBvi6T=EB==NyN?7k%Z`r}K$)u|YYF~a zMy@~t0MMlH(A{w4F8UH1copwBAcKQlayTUo02Fr75ul>V1pq4%DKsehm#UBQ?!uKZ zE;gShg_}B6zMYiHtIPikP8sSUmaMAF3patFh}tb%_9`yhe`MB`I&Eq5S@Iq4R?s0- z$ayZ(X()JuwC?a1d|?K?P>zz7^8;~7?9uW}j`H>3q4C^Qb$Mo@9WnH43vs8|vd*B3 zHBO>-g@Is;yIug4bG&`VD_2LsAoF zLcp?gLJsi1Nz&9uw!PHlEv2jl5}C+hK4N(%{>BI+<|#D*G@6Bs@NbxkJsH&yi(HK(u86UuX|NU)M1C8;}er6K@p`T0XEWv%zIg#o(07UE9d$_|5FA!kX zvPicb?z*`o>soP+;%*_Tmf9@B4VfL|=e~`iMo+e_mvS7z~RN(at zq?n)N!Q19wdCX1ZfL%hjEna72E037PHC-AJ9IPVY-lv#kzf*@AvMDHPt3#vr9TT+UVAW)4ZaHzf$KP$}b6) zakQ9Il`H(X?Fn9H;x({sHE|#t8m1N^#?@n8W77{tg-w_T5hr|E*`#)=bHCcY!Zx0C zPGlkVzVK8HwPd^Ly*NZNPYU=y##pcCcEuF~giT*dWP< zREhZt*D%79!#t{~c{om^AVAK>P@~;#L)-evsWFnyQ>u}O&T1sF*}r)~12QitN7pDt z*BtjKg=$WKeWt$!drWgjt+}0$HdTgWbdzAEcZXp*1_NZc1>_?fL$@(s)|1kwy1oWivHsK)UL@y{jhC4+nn~Sp&!16Qz=2T3#-&H N0a93GXiIQB?;j2ScJlxL literal 0 HcmV?d00001 diff --git a/assets/images/article_tree_folder_folded.png b/assets/images/article_tree_folder_folded.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5fedcfc9c2ece8a5bac62b4331b946cd84c9d6 GIT binary patch literal 771 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9GG!XV7ZFl&wk0|V1m zPZ!6KiaBp@8TMUvkZ60D|3-m>Tg4-zQ8~x;G}n=^+wXWS7-bZ6V^v<|S{&OZ=;Sa_ z_`tmeJEck7Wws)I!Z$fu4krJuo^RCp)%|nYn-nERpiNNFu#wYO|5$%{-;yHDmm!z7 z+@2VvE%G&Pa*p7<{QXPhmc7qnd?Qw`n>j6EUcE+L_=)*#;^D_^zC4!`1#S7H5{`e7MiGyvwS)a$nzbt1O@>g9&FS%VUG?$u&2>DaA2* zT(X&-$5$u5;>frCH-E4SEcx)`n7*GQ+w!mZW_3VW6OYXo?{GzZQ&pP}YV+5hi%oEhS>(%D%aEG;W@V0gxHK!$OK zGD9&-LLb8!!3Ikv1NXrzn9a4|MGaTU(rw#(8P`}zuw-7jwoS1iM=|sAL4z09(zb7_ z&SGqt{C4RSS?^r}+nWtFRAN1D7H-`JOHx2O;Y}Tr(5diU+m=471qpk)`njxgN@xNA D{gE>m literal 0 HcmV?d00001 diff --git a/assets/images/article_tree_folder_yellow.png b/assets/images/article_tree_folder_yellow.png deleted file mode 100644 index 7df9d28afc19c9fb9efa8bbc51b2da3d524d14f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^4nS8U}fi7AzZCsS=07#Nv7T^vIyZoRo;$aTm;p!MNNwWbKyBX)DwE*J1FX8Fis z>A9!j1uxGuvw4U0%rAK9OxeMlot_;rHn->gSa^G)Pw^ZgZ)T&*-e^&WP7^BLZ9o|0E6V$(5 zYV+Q?k?Vu+%Oho3uNhpIy}xKMwQ!#2G&QxT{rBeHd8c1#bN|tnKP5&6!EuXYw{9*g zx2jxy@!h#gPwMBgyq>NA^eGV_fn|Z|o&BK=rwkmPww(@sZ10tgTe~DWM4f={$%i diff --git a/assets/images/article_tree_folder_yellow_document.png b/assets/images/article_tree_folder_yellow_document.png deleted file mode 100644 index a7143ab42361010fb5c6232464aa7b29e5886d8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmeAS@N?(olHy`uVBq!ia0vp^4nS8U}fi7AzZCsS=07#OQPT^vIyZoRp--|KLI2-}C^{L2zY7786T-Ld1^bOHCx z+8TC_Y3yG)Ke03|*nRZkQTBoj&E4HkSXarqc>PzI7raPf#Zkw}+3(696fjHb-G6`Q zUdYAiRW`S?K3-aOfBm~-4}ItJ=&1AWJM4W%_RmGT=XcoEm{!>Q3FWMw`$^Nnkk{e) zWc|f`lGXQJ8WtS+o8*=;n=K&iq-yo-_-)7TFh;WeF59`d%Se2mM<`=RY$s2HqSZF0 znvfMI-*wI1X})B)%e?*n-Ustl`A^}CPv6V6kM)E3_Z{Wg#;@-ERsDYSynS;im%YCK zLzaxSCvsQ4EdKQ;V7L69?F%w3PHFgVRywfg`hus^oc)|@_etE9y~>x6dvxx)$DVHb ze+~EYsx49dv+BN1%lc~1u#-ABxBA?>HtEf+Whc*Dac=uB$OH^vB0x2RQ1@qbCV@yE rfz15hyAJa&OjUQ>)HdmFYy%_1O}`+SghMtvK<0S5`njxgN@xNAG!MYh diff --git a/assets/stylesheets/knowledgebase.css b/assets/stylesheets/knowledgebase.css index 746a89f7..bd3fc11b 100644 --- a/assets/stylesheets/knowledgebase.css +++ b/assets/stylesheets/knowledgebase.css @@ -250,21 +250,21 @@ div.attachments { } .icon-folder-expand:before, .icon-folder-collapse:before { - width: 19px; - height: 15px; + width: 17px; + height: 17px; } .icon-article:before { - width: 12px; - height: 14px; + width: 15px; + height: 15px; } .icon-folder-expand:before { - background-image: url("../images/article_tree_folder_yellow_document.png"); + background-image: url("../images/article_tree_folder_expanded.png"); } .icon-folder-collapse:before { - background-image: url("../images/article_tree_folder_yellow.png"); + background-image: url("../images/article_tree_folder_folded.png"); } .icon-article:before { From 697a7fdc710943c144e0535329b669fd71eb43a0 Mon Sep 17 00:00:00 2001 From: Mario Simanjuntak Date: Wed, 23 Dec 2020 17:46:05 +0700 Subject: [PATCH 5/5] Updated Article Icon and size --- assets/images/article_tree_document.png | Bin 1184 -> 1953 bytes assets/stylesheets/knowledgebase.css | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/assets/images/article_tree_document.png b/assets/images/article_tree_document.png index 751c503f36541ddd034a3a6deada41f7a44794d6..97184736ac9a36d08574664202889e92329b66be 100644 GIT binary patch delta 1937 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 JtaD0e0swmv7MK74 diff --git a/assets/stylesheets/knowledgebase.css b/assets/stylesheets/knowledgebase.css index bd3fc11b..f5c2493c 100644 --- a/assets/stylesheets/knowledgebase.css +++ b/assets/stylesheets/knowledgebase.css @@ -250,13 +250,13 @@ div.attachments { } .icon-folder-expand:before, .icon-folder-collapse:before { - width: 17px; - height: 17px; + width: 20px; + height: 20px; } .icon-article:before { - width: 15px; - height: 15px; + width: 18px; + height: 18px; } .icon-folder-expand:before { @@ -275,6 +275,18 @@ div.attachments { color: #fff; } +#article-tree.tj_container li span.tj_icon { + margin-right: 4px; +} + +#article-tree.tj_container li .tj_description span.tj_icon { + height: 15px; +} + +#article-tree.tj_container li .tj_description.tj_leaf span.tj_icon { + height: 14px; +} + /* If you want custom default thumbnails by category, add them here! */ /*